下载地址应用宝: https://a.app.qq.com/o/simple.jsp?pkgname=com.hw.okm
抓包看到请求体有 sign 参数 及响应是加密的状态
360壳加固脱壳并修复~
过root检测,该app有多方面检测root~
我们直接修改smali过掉~
java层循环检测root
我是直接将 public static final String COMMAND_SU = “su”;
更改为: public static final String COMMAND_SU = “zhihuijiance”;
已达到一个去除root检测的方案。
package com.hw.okm.g;
DispatchConstants.SIGN, 就是"sign"
那么就剩 s.a(s.a(sb.deleteCharAt(sb.length() - 1).toString()) + com.hw.okm.c.a.f11289d
sb.deleteCharAt(sb.length() - 1).toString()
盲猜 就是 参数加到sb 每个后面都会加 “&” 最后去掉最后那个 “&” 然后走s.a()方法
走完后 再 加上 f11289d 再走一遍MD5方法 也就是套两层md5
f11289d 是:
def md5_ersanli(data):
_sign = ''
for key, values in data.items():
_sign += key + "=" + values + "&"
encryptValue = bytes(_sign[:-1].encode("utf-8"))
sign_encryption = hashlib.md5(encryptValue).hexdigest()
f6967d = "qB5LB51bUHAjFBA4Bh3YQnoDRrC912KWumxLyDzvOojP6VBH6fAwoHE6w0t58H5MtEsQDpmeVUV2C5JOjCAa4kMp35R8aIZ4fWD6uD9iDvnUHH6XTKKF1KzoJ4ddA8w39I05Re7qQi9vDtpC8iSFEIrvgmo4b01WhcELvi3DIli0ik9O1AmkEvSXKJB0DXDc1nFrF4W5Gg3Vq3rcsabKR340uvcDaPuDcd2XC7771HX1U80Ed1pWQZtsAFDtVD5Y";
encryptValue = bytes((sign_encryption + f6967d).encode("utf-8"))
sign = hashlib.md5(encryptValue).hexdigest()
return sign
frida走起:
# -*- coding: utf-8 -*-
# @Author : Codeooo
# @Time : 2021/11/18
import frida, sys
jscode ="""
Java.perform(function () {
var utils = Java.use('com.hw.okm.g.a');
utils.e.implementation = function () {
console.log("======Hook Start...=========");
send(arguments[0]);
send(arguments[1]);
send(arguments[2]);
var map = arguments[2]
for (var key in map) {
console.log(key);
}
send("==========以上是返回值==========");
console.log(this.e(arguments[0],arguments[1],arguments[2]));
return this.e(arguments[0],arguments[1],arguments[2]);
}
});
"""
res ="""
Java.perform(function () {
var utils = Java.use('com.hw.okm.utils.s');
utils.a.implementation = function () {
console.log("======Hook Start...=========");
send(arguments[0]);
send("==========以上是返回值==========");
console.log(this.a(arguments[0]));
return this.a(arguments[0]);
}
});
"""
def message(message, data):
if message["type"] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach('com.hw.okm')
# script= process.create_script(jscode)
script= process.create_script(res)
script.on("message", message)
script.load()
sys.stdin.read()
响应这块也进行了加密 :
我们请求下:
发现响应是这样的 ,看源码解密流程:
密钥找到了,那么第一个参数应该是刚刚响应加密那一串
package com.hw.okm.utils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/* loaded from: classes2.dex */
public class a {
/* renamed from: a reason: collision with root package name */
private static final char[] f12700a = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/* renamed from: b reason: collision with root package name */
private static final char[] f12701b = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/* renamed from: c reason: collision with root package name */
private static String f12702c = "utf-8";
/* renamed from: d reason: collision with root package name */
private static String f12703d = "AES/ECB/PKCS5Padding";
/* renamed from: e reason: collision with root package name */
private static String f12704e = "AES";
public static byte[] a(CharSequence charSequence) {
if (y.e(charSequence.toString())) {
return null;
}
int length = charSequence.length();
if ((length & 1) != 0) {
charSequence = "0" + ((Object) charSequence);
length = charSequence.length();
}
byte[] bArr = new byte[length >> 1];
int i = 0;
int i2 = 0;
while (i < length) {
int i3 = i + 1;
i = i3 + 1;
bArr[i2] = (byte) (((j(charSequence.charAt(i), i) << 4) | j(charSequence.charAt(i3), i3)) & 255);
i2++;
}
return bArr;
}
public static String b(String str) {
return c(str, com.hw.okm.c.a.f11288c);
}
public static String c(String str, String str2) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(str2.getBytes(), f12704e);
Cipher instance = Cipher.getInstance(f12703d);
instance.init(2, secretKeySpec);
return new String(instance.doFinal(a(str)), "UTF-8");
} catch (Exception e2) {
e2.printStackTrace();
return null;
}
}
protected static char[] d(byte[] bArr, char[] cArr) {
int length = bArr.length;
char[] cArr2 = new char[length << 1];
int i = 0;
for (int i2 = 0; i2 < length; i2++) {
int i3 = i + 1;
cArr2[i] = cArr[(bArr[i2] & 240) >>> 4];
i = i3 + 1;
cArr2[i3] = cArr[bArr[i2] & 15];
}
return cArr2;
}
public static String e(byte[] bArr) {
return f(bArr, true);
}
public static String f(byte[] bArr, boolean z) {
return g(bArr, z ? f12700a : f12701b);
}
protected static String g(byte[] bArr, char[] cArr) {
return new String(d(bArr, cArr));
}
public static String h(String str) {
return i(str, com.hw.okm.c.a.f11288c);
}
public static String i(String str, String str2) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(str2.getBytes(), f12704e);
Cipher instance = Cipher.getInstance(f12703d);
byte[] bytes = str.getBytes(f12702c);
instance.init(1, secretKeySpec);
return e(instance.doFinal(bytes));
} catch (Exception e2) {
e2.printStackTrace();
return null;
}
}
private static int j(char c2, int i) {
int digit = Character.digit(c2, 16);
if (digit < 0) {
return 0;
}
return digit;
}
}
看下源码
Over , 其实加密这块还比较简单都是标准的一些算法, 该app难点在于:
去除360加固,修改dex,并去除root检测,过掉反调试。
# -*- coding: utf-8 -*-
# @Author : Codeooo
# @Time : 2022/04/26
import re
import json
import time
import uuid
import hashlib
import requests
import binascii
from collections import OrderedDict
from Crypto.Cipher import AES
def md5_ersanli(data):
_sign = ''
for key, values in data.items():
_sign += key + "=" + values + "&"
encryptValue = bytes(_sign[:-1].encode("utf-8"))
sign_encryption = hashlib.md5(encryptValue).hexdigest()
f6967d = "qB5LB51bUHAjFBA4Bh3YQnoDRrC912KWumxLyDzvOojP6VBH6fAwoHE6w0t58H5MtEsQDpmeVUV2C5JOjCAa4kMp35R8aIZ4fWD6uD9iDvnUHH6XTKKF1KzoJ4ddA8w39I05Re7qQi9vDtpC8iSFEIrvgmo4b01WhcELvi3DIli0ik9O1AmkEvSXKJB0DXDc1nFrF4W5Gg3Vq3rcsabKR340uvcDaPuDcd2XC7771HX1U80Ed1pWQZtsAFDtVD5Y";
encryptValue = bytes((sign_encryption + f6967d).encode("utf-8"))
sign = hashlib.md5(encryptValue).hexdigest()
return sign
def get_deviceId():
return "".join(str(uuid.uuid4()).split("-"))
def decrypt(decrData, password='rFQJTvMFzm8MtBRy'):
if isinstance(password, str):
password = password.encode('utf8')
cipher = AES.new(password, AES.MODE_ECB)
plain_text = cipher.decrypt(binascii.a2b_hex(decrData))
# print("plain_text", str(plain_text, 'utf-8').rstrip('\r'))
# return plain_text.decode('utf8').rstrip('\0')
return bytes.decode(plain_text).rstrip('\r')
def run():
headers = {
"User-Agent": "okm/7.2.5",
"Host": "api.ersanli.cn",
}
_area = "110101"
post_data = OrderedDict()
post_data["page"] = "1"
post_data["size"] = "20"
post_data["clientType"] = "3"
post_data["deviceId"] = get_deviceId()
post_data["appId"] = "1"
post_data["appVersion"] = "7.2.5"
post_data["osVersion"] = "7.2.5"
post_data["timestamp"] = str(int(time.time()))
post_data["nonce_str"] = str(uuid.uuid4())
post_data["location"] = _area
post_data["sign"] = md5_ersanli(post_data)
url = 'https://api.ersanli.cn/kilos/apis/news/'
data = requests.post(url, headers=headers, data=post_data).json()
print(data)
decrypt_data = decrypt(data.get("dataJson"), 'rFQJTvMFzm8MtBRy')
print(json.loads(re.findall(r'{.*}', decrypt_data, re.S)[0]))
if __name__ == '__main__':
run()