继续练习吧,跟着案例来走,加油!
一:绕过强制更新
从某个网站下载下来的apk,由adb install 到手机中发现居然要更新?这能忍?看看咱们怎么绕过它
方法一:断网-->打开apk--->联网
发现绕是绕过了,但是却抓不到包了。
方法二:反编译寻找更新位置,然后hook它的函数
public static void showUpdateDialog() {
new AHAlertDialog.Build(AppManager.getInstance().getTopActivity()).setButtonOrientation(2).setTitle(ContextProvider.getContext().getString(R.string.tip)).setTitleVisibility(0).setAutoClickDismiss(false).setMessage("亲爱的商家,您的车智赢+APP版本过低啦,建议您尽快升级,发现更多功能~").addButton("更新APP程序").setOnItemClickListener(new AHAlertDialog.AHAlertDialogListener() {
@Override
public void onItemClickListener(int i, String str) {
SystemUtil.openBrowser(ContextProvider.getContext(), "https://appdownload.che168.com/usedcar/csy/index.html?pvareaid=106103");
}
}).create().show();//只要执行Show,就会弹窗,要求更新---hook这show方法,让它不运行
}
1.1打印前台运行的包名
import frida
# 获取设备信息
rdev = frida.get_remote_device()
# 枚举所有的进程
# processes = rdev.enumerate_processes()
# for process in processes:
# print(process)
# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print(front_app) # Application(identifier="com.che168.autotradercloud", name="车智赢+", pid=22134, parameters={}) 后面咱们会看包名和app名
1.2使用hook方式,让弹窗不执行(spawn方式)
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2024/1/22 21:16
@Description:
'''
####### hook代码
import frida
import sys
rdev = frida.get_remote_device()
pid = rdev.spawn(["com.che168.autotradercloud"])
session = rdev.attach(pid)
scr = """
Java.perform(function () {
var AlertDialog = Java.use('androidx.appcompat.app.AlertDialog');
AlertDialog.show.implementation = function(){
console.log("执行了");
//this.show();
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()
二:抓包分析
'''
url = https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx
请求头:
:method POST
:path /tradercloud/sealed/login/login.ashx
:authority dealercloudapi.che168.com
:scheme https
cache-control public, max-age=0
traceid atc.android_f64fc887-e707-41c7-aba5-1e6f419a9ebd
content-type application/x-www-form-urlencoded
content-length 254
accept-encoding gzip
user-agent okhttp/3.14.9
非常正常
请求体: 全都要带
_appid atc.android 不变
_sign 6B05FA1121E46EFA6437E97D4EC93C8D 要逆
appversion 3.29.0 不变
channelid csy 不变
pwd e10adc3949ba59abbe56e057f20f883e 要逆
udid /sn9R5SQNWEh/0lpSTkSHanImfSaPWXj0gfgJa9+fztvrTEsdbBIHojsjqA1 8tI2FSWnwx7kBtHyVHFFFWcTsw== 要逆向
username 13576351684 账号名 不用逆向
'''
三:pwd逆向分析
搜索sealed/login/login.ashx ,发现只有一个结果
查找用例 LOGIN_UR
轻轻松松就找到了加密的位置,发现就是对输入的值进行MD5加密,而且没加盐
public static void loginByPassword(String str, String str2, String str3, ResponseCallback<UserBean> responseCallback) {
HttpUtil.Builder builder = new HttpUtil.Builder();
builder.tag(str).method(HttpUtil.Method.POST).signType(1).url(LOGIN_URL).param("username", str2).param("pwd", SecurityUtil.encodeMD5(str3));
doRequest(builder, responseCallback, new TypeToken<BaseResult<UserBean>>() { // from class: com.che168.autotradercloud.user.model.UserModel.5
}.getType());
}
四:udid逆向分析
发现有那么多结果,很明显,map.put很可疑,因为传过去的param都是先put进map字典里,然后拼接的,简单来说就是将请求体里的
sign、udid等参数获得的。
public static void appendUdid(Context context, Map<String, String> map) {
map.put("udid", getUDID(context));
}
public static void appendUserKey(Context context, Map<String, String> map) {
String userKey = UserInfoUtil.getUserKey(context);
if (!TextUtils.isEmpty(userKey)) {
map.put("userkey", userKey);
}
}
public static synchronized String getUDID(Context context) {
String encode3Des;
synchronized (AHAPIHelper.class) {//往字符串里面append添加值
StringBuilder sb = new StringBuilder();
sb.append(AHDeviceUtil.getDeviceId(context));//随机uuid
sb.append(HiAnalyticsConstant.REPORT_VAL_SEPARATOR);//'|'
sb.append(System.nanoTime());//开机到现在的时间
sb.append(HiAnalyticsConstant.REPORT_VAL_SEPARATOR);//'|'
sb.append(UserInfoUtil.getUserDeviceId(context));//用户设备id
encode3Des = SecurityUtil.encode3Des(context, sb.toString());//DES加密
}
return encode3Des;
}
诺,猜的没错,这地方很可疑,咱hook一下encode3Des看看
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : whitedog
@time : 2023/9/28 10:37
@Description:
'''
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("车智赢+")
scr = """
Java.perform(function () {
// 包.类
var SecurityUtil = Java.use("com.autohome.ahkit.utils.SecurityUtil");
// Hook,替换
SecurityUtil.encode3Des.implementation = function(context,str){
// 执行原来的方法
console.log("传入的参数str为:",str);
var res = this.encode3Des(context,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()
'''
传入的参数str为: 9382a1f0-2df2-3e1d-9faa-8e93d4cc71d4|10496729500147|367368 //猜的没错
加密后为: /sn9R5SQNWEh/0lpSTkSHanImfSaPWXj0gfgJa9+fzsmJhgNiLKcZ2Ys9qfz HOIHuC6cOn/0W7i35iIcX6Fh+Q==
iv = appapich
'''
4.1 hook_des_key
public static String getDesKey(Context context) {
if (TextUtils.isEmpty(mDesKey)) {
getSignDesKey(context);
}
return mDesKey;
}
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/9/28 10:37
@Description:
'''
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("车智赢+")
scr = """
Java.perform(function () {
// 包.类
var AHAPIHelper = Java.use("com.autohome.ahkit.AHAPIHelper");
// Hook,替换
AHAPIHelper.getDesKey.implementation = function(context){
// 执行原来的方法
//console.log("传入的参数context为:",context);
var res = this.getDesKey(context);
console.log("mDesKey:::",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()
#appapiche168comappapiche168comap
4.2python模拟udid
import base64
from Crypto.Cipher import DES3
BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
# 3DES的MODE_CBC模式下只有前24位有意义
key = b'appapiche168comappapiche168comap'[0:24]
iv = b'appapich'
plaintext = pad("9382a1f0-2df2-3e1d-9faa-8e93d4cc71d4|31672388334956|367368").encode("utf-8")
# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
res = base64.b64encode(result)
print(res)
五:_sign逆向分析
直接搜索"_sign发现有五个结果
发现前四个分别hook都找不到,那就只能干第四个了
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/9/28 10:37
@Description:
'''
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
session = rdev.attach("车智赢+")
scr = """
Java.perform(function () {
// 包.类
var LaunchModel = Java.use("com.che168.autotradercloud.launch.model.LaunchModel");
// Hook,替换
LaunchModel.lambda$initRequestCommonParams$0.implementation = function(i,map){
// 执行原来的方法
console.log("传入的参数map为:",map);
console.log("传入的参数i为:",i);
var res = this.lambda$initRequestCommonParams$0(i,map);
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()
'''
传入的参数map为: {pwd=e10adc3949ba59abbe56e057f20f883e, username=13576251688}
传入的参数i为: 1
结果为: {_appid=atc.android, _sign=A9E7B43116657D937AE0CF6E1B9BC893, appversion=3.29.0, channelid=csy, pwd=e10adc3949ba59abbe56e057f20f883e, udid=/sn9R5SQNWEh/0lpSTkSHanImfSaPWXj0gfgJa9+fzv0oDcVAIj9xYnt6cBA FTNpKBo3gQVaUvhxkC+6d/eYog==, username=13576251688}
'''
然后再点进去signByte,观察观察
public static final String KEY_AUTOHOME = "@7U$aPOE@$";
public static final String KEY_SHARE = "moc.861ehc.relaed.bup.wyfv";
public static final String KEY_V1 = "com.che168.www";
public static final String KEY_V2 = "W@oC!AH_6Ew1f6%8";
public final String signByType(@SignType int i, TreeMap<String, String> paramMap) {
Intrinsics.checkNotNullParameter(paramMap, "paramMap");
StringBuilder sb = new StringBuilder();
String str = KEY_V1;
if (i != 0) { //i=1
if (i == 1) {
str = KEY_V2;
} else if (i == 2) {
str = KEY_SHARE;
} else if (i == 3) {
str = KEY_AUTOHOME;
}
}
sb.append(str);
for (String str2 : paramMap.keySet()) {
sb.append(str2); //循环获得key和value 并拼接在sb上
sb.append(paramMap.get(str2));
}
sb.append(str);
String encodeMD5 = SecurityUtil.encodeMD5(sb.toString()); //进行md5加密
if (encodeMD5 != null) {
Locale ROOT = Locale.ROOT;
Intrinsics.checkNotNullExpressionValue(ROOT, "ROOT");
String upperCase = encodeMD5.toUpperCase(ROOT);
Intrinsics.checkNotNullExpressionValue(upperCase, "this as java.lang.String).toUpperCase(locale)");
if (upperCase != null) {
return upperCase;
}
}
return "";
}
python模拟构建sign
# md5加密
def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
data_dict = {
"_appid": "atc.android",
"appversion": "3.28.0",
"channelid": "csy",
"pwd": md5(passwrod),
"udid": udid,
"username": username
}
result ="W@oC!AH_6Ew1f6%8"+ "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])+"W@oC!AH_6Ew1f6%8"
sign = md5(un_sign_string).upper()
如果仅仅只是想要结果,那看到这里就行了。
但是,咱为了练习,必须要思考"deskey"是从哪里搞来的?
很明显就是getSignkey()返回的,一直追踪到这里
发现竟然是在so层,那么我们直接反编译吧,libnative-lib.so
看到很混乱,导入头文件试试
看的舒服多了,说明咱的目标就是DES3_KEY
这个就是咱要的key,固定字符串了
六 代码整合
# -*- coding: utf-8 -*-
'''
@IDE : PyCharm
@version : 3.9
@Auth : gouzi
@time : 2023/9/28 10:10
@Description:
'''
import hashlib
import uuid
import random
import base64
from Crypto.Cipher import DES3
import requests
# encode3Des 算法
def des3(data_string):
BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
# 3DES的MODE_CBC模式下只有前24位有意义
key = b'appapiche168comappapiche168comap'[0:24]
iv = b'appapich'
plaintext = pad(data_string).encode("utf-8")
# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
return base64.b64encode(result).decode('utf-8')
# md5加密
def md5(data_string):
obj = hashlib.md5()
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
def run():
username = "13576251668"
passwrod = "111111"
imei = str(uuid.uuid4()) # 随机uuid
nano_time = random.randint(5136066335773, 7136066335773)# 开机时间
device_id = '358908' # 可以为空,也可以358908
udid = des3(f"{imei}|{nano_time}|{device_id}")
print(udid)
data = "W@oC!AH_6Ew1f6%8"
data_dict = {
"_appid": "atc.android",
"appversion": "3.28.0",
"channelid": "csy",
"pwd": md5(passwrod),
"udid": udid,
"username": username
}
result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
un_sign_string = f"{data}{result}{data}"
sign = md5(un_sign_string).upper()
data_dict['_sign'] = sign
res = requests.post(
url="https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx",
data=data_dict,
verify=False
)
print(res.text)
if __name__ == '__main__':
run()
运行结果:
大功告成!