App信息 包名:com.yoloho.dayima
root检测
狐妖面具 使用狐妖面具配置排除列表
脚本绕过 开源项目脚本:https://github.com/AshenOneYe/FridaAntiRootDetection
反编译Apk找root检测逻辑 Jadx搜索Root设备
双击跳转
isRooted()
这里直接返回false就可以了
isSimulater() 获取build.prop文件中的属性进行检测,返回false即可。
hook_root 1 2 3 4 5 6 7 8 9 10 11 function hook_root ( ){ Java .perform (function ( ){ let MiscUtil = Java .use ("com.yoloho.libcore.util.MiscUtil" ); MiscUtil ["isRooted" ].implementation = function ( ) { console .log (`MiscUtil.isRooted is called` ); let result = this ["isRooted" ](); console .log (`MiscUtil.isRooted result=${result} ` ); return false ; }; }) }
就可以进入app了
登录抓包分析 输入账号密码:18888888888 12345678
抓包数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 url: https: params: { "device" : "63122cb8da575c7e71ace0dccb1e35b0f2a43758" , "ver" : "900" , "screen_width" : "1080" , "screen_height" : "2028" , "model" : "Pixel 3" , "sdkver" : "29" , "platform" : "android" , "releasever" : "10" , "channel" : "vivobbg" , "latt" : "0" , "lngt" : "0" , "networkType" : "0" , "token" : "" , "userStatus" : "0" , "oaid" : "" , "installDate" : "0" } headers: { "Accept-Encoding" : "gzip" , "User-Agent" : "Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QQ3A.200705.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36" , "Content-Length" : "211" , "Content-Type" : "application/x-www-form-urlencoded" , "Host" : "uicapi.yoloho.com" , "Connection" : "Keep-Alive" } body: { "username" : "18888888888" , "password" : "ET4Sdfjs+/VkbhxXerUBFg==" , "sign" : "c6337760363ed264b287178c340f9cb8" , "androidid" : "9000290bea7be29b" , "mac" : "EE:9E:96:57:AB:FD" , "imei" : "" , "density" : "2.75" , "brand" : "google" , "oaid" : "" , "installDate" : "0" }
这里面需要解的参数有device、password、sign
逆向 device参数 jadx搜索device,出现很多结果,可以双击每个结果查看他们的上下文中的其他参数与接口中的参数进行比较,定位到device参数的位置。也可以搜索接口中其他参数,在其他参数的上下文中可以找到device。
定位到如下位置。
device的值是经过URL编码后的getDeviceCode()的值
getDeviceCode()
这里面通过setDeviceCode方法来设置device的值
setDeviceCode() 如下是源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public void setDeviceCode () { String str; String str2; String str3; if (NetUtil.getSharedPreferences(SettingsConfig.KEY_USER_PERMISSION, false ) && deviceCode == null ) { deviceCode = "NotFound" ; try { try { str = Build.BOARD + Build.BRAND + Build.CPU_ABI + Build.DEVICE + Build.DISPLAY + Build.HOST + Build.ID + Build.MANUFACTURER + Build.MODEL + Build.PRODUCT + Build.TAGS + Build.TYPE + Build.USER; } catch (Exception e2) { e2.printStackTrace(); str = null ; } try { str2 = AppInfo.getAndroidId(); } catch (Exception e3) { e3.printStackTrace(); str2 = null ; } try { str3 = DayimaUtil.getPhoneMac(); } catch (Exception e4) { e4.printStackTrace(); str3 = null ; } String str4 = "" ; if (!Build.MODEL.equals("vivo X1w" ) || !Build.VERSION.RELEASE.equals(FaceEnvironment.SDK_VERSION)) { try { BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter(); if (defaultAdapter != null ) { str4 = defaultAdapter.getAddress(); } } } String str5 = ((String) null ) + str2 + str + str3 + str4; MessageDigest messageDigest = MessageDigest.getInstance("sha-1" ); messageDigest.update(str5.getBytes()); byte [] digest = messageDigest.digest(); StringBuffer stringBuffer = new StringBuffer (); for (byte b : digest) { String lowerCase = Integer.toHexString(b & 255 ).toLowerCase(Locale.getDefault()); if (lowerCase.length() < 2 ) { lowerCase = "0" + lowerCase; } stringBuffer.append(lowerCase); } deviceCode = stringBuffer.toString(); } } }
分析
这里面先是拼接了一个字符串String str5 = ((String) null) + str2 + str + str3 + str4;然后对这个字符串进行了sha1签名
str2
1 str2 = AppInfo.getAndroidId();
获取AndroidId
str
1 str = Build.BOARD + Build.BRAND + Build.CPU_ABI + Build.DEVICE + Build.DISPLAY + Build.HOST + Build.ID + Build.MANUFACTURER + Build.MODEL + Build.PRODUCT + Build.TAGS + Build.TYPE + Build.USER;
设备指纹信息
str3
1 str3 = DayimaUtil.getPhoneMac();
获取设备Mac地址
str4
1 str4 = defaultAdapter.getAddress();
获取的是蓝牙地址
把这些内容拼接起来进行SHA1签名,然后全小写就是deviceid值
hook setDeviceCode 通过hook查看传入的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function hook_device ( ) { var flag = false ; Java .perform (function ( ) { let PeriodAPIV2 = Java .use ("com.yoloho.controller.api.PeriodAPIV2" ); PeriodAPIV2 ["setDeviceCode" ].implementation = function ( ) { flag = true ; console .log (`========== PeriodAPIV2.setDeviceCode is called ==========` ); this ["setDeviceCode" ](); }; var messageDigest = Java .use ("java.security.MessageDigest" ); var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); messageDigest.update .overload ('[B' ).implementation = function (data ) { if (flag) { console .log ("MessageDigest.update('[B') is called!" ); console .log (ByteString .of (data).utf8 ()); console .log ("-------------------------------------------------------" ); flag = false ; } return this .update (data); } }); } hook_device ();
hook 结果
1 2 3 4 ========== PeriodAPIV2.setDeviceCode is called ========== MessageDigest.update('[B') is called! null9000290bea7be29bbluelinegooglearm64-v8abluelineQQ3A.200705.002abfarm836QQ3A.200705.002GooglePixel 3bluelinerelease-keysuserandroid-buildEE:9E:96:57:AB:FD02:00:00:00:00:00 -------------------------------------------------------
password参数 搜索password
privateStrHandle的值是通过privateStrHandle()方法返回的,它传入的参数是密码和用户名
privateStrHandle()
通过AES加密返回一个encrypt
这里的三个参数,第一个参数明文是密码,第二个参数密钥是用户名进行MD5后取前16字节然后转小写,第三个参数iv向量是yoloho_dayima!%_
hook privateStrHandle() 1 2 3 4 5 6 7 8 9 10 11 12 function hook_aes ( ) { Java .perform (function ( ) {let DayimaPrivateUtil = Java .use ("com.yoloho.libcore.util.DayimaPrivateUtil" ); DayimaPrivateUtil ["privateStrHandle" ].implementation = function (str, str2 ) { console .log (`DayimaPrivateUtil.privateStrHandle is called: str=${str} , str2=${str2} ` ); let result = this ["privateStrHandle" ](str, str2); console .log (`DayimaPrivateUtil.privateStrHandle result=${result} ` ); return result; }; }); } hook_aes ();
hook 结果
1 2 DayimaPrivateUtil.privateStrHandle is called: str=12345678, str2=18888888888 DayimaPrivateUtil.privateStrHandle result=ET4Sdfjs+/VkbhxXerUBFg==
测试验证
没问题
sign参数还原 找到sign的生成位置
它是通过Crypt.encrypt_data()方法生成的,它里面的参数是由DeviceCode、"user/login"、str、str2拼接而成的
encrypt_data()
这是一个native方法
IDA分析 找到静态函数
双击跳转
sub_1DA0()
接下来使用rpc调用
hook encrypt_data() 1 2 3 4 5 6 7 8 9 10 11 12 13 function hook_encrypt_data ( ) { Java .perform (function ( ) { let Crypt = Java .use ("com.yoloho.libcore.util.Crypt" ); Crypt ["encrypt_data" ].implementation = function (j2, str, j3 ) { console .log (`Crypt.encrypt_data is called: j2=${j2} , str=${str} , j3=${j3} ` ); let result = this ["encrypt_data" ](j2, str, j3); console .log (`Crypt.encrypt_data result=${result} ` ); return result; }; }); } hook_encrypt_data ();
hook 结果
1 2 Crypt.encrypt_data is called: j2=0, str=63122cb8da575c7e71ace0dccb1e35b0f2a43758user/login18888888888ET4Sdfjs+/VkbhxXerUBFg==, j3=85 Crypt.encrypt_data result=c6337760363ed264b287178c340f9cb8
rpc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import fridadevice = frida.get_remote_device() process = device.attach("大姨妈" ) js_code = """ rpc.exports = { endata: function (j2,str,j3) { var res; Java.perform(function () { let Crypt = Java.use("com.yoloho.libcore.util.Crypt"); let encrypted_data = Crypt.encrypt_data(j2,str,j3); res = encrypted_data; }); return res; } } """ script = process.create_script(js_code) script.load() en_data = script.exports_sync.endata(0 , "63122cb8da575c7e71ace0dccb1e35b0f2a43758user/login18888888888ET4Sdfjs+/VkbhxXerUBFg==" , 85 ) print (en_data)
Python实现登录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import requestsurl = "https://uicapi.yoloho.com/user/login" params = { "device" : "63122cb8da575c7e71ace0dccb1e35b0f2a43758" , "ver" : "900" , "screen_width" : "1080" , "screen_height" : "2028" , "model" : "Pixel 3" , "sdkver" : "29" , "platform" : "android" , "releasever" : "10" , "channel" : "vivobbg" , "latt" : "0" , "lngt" : "0" , "networkType" : "0" , "token" : "" , "userStatus" : "0" , "oaid" : "" , "installDate" : "0" } headers = { "Accept-Encoding" : "gzip" , "User-Agent" : "Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QQ3A.200705.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36" , "Content-Length" : "211" , "Content-Type" : "application/x-www-form-urlencoded" , "Host" : "uicapi.yoloho.com" , "Connection" : "Keep-Alive" } body = { "username" : "18888888888" , "password" : "ET4Sdfjs+/VkbhxXerUBFg==" , "sign" : "c6337760363ed264b287178c340f9cb8" , "androidid" : "9000290bea7be29b" , "mac" : "EE:9E:96:57:AB:FD" , "imei" : "" , "density" : "2.75" , "brand" : "google" , "oaid" : "" , "installDate" : "0" } response = requests.post(url, params=params, headers=headers, data=body) print (response.status_code)print (response.text)
Unidbg调用encrypt_data() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class MainActivity extends AbstractJni { private final AndroidEmulator emulator; private final Memory memory; private final VM vm; public MainActivity () { emulator = AndroidEmulatorBuilder .for64Bit() .build(); memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver (23 )); vm = emulator.createDalvikVM(new File ("unidbg-android/src/test/java/com/dym/encryptdata/DYM_8.9.0.apk" )); vm.setJni(this ); vm.setVerbose(true ); vm.loadLibrary("Crypt" , true ); } public void encrypt_data () { DvmClass Crypt = vm.resolveClass("com.yoloho.libcore.util.Crypt" ); StringObject stringObject = Crypt.callStaticJniMethodObject(emulator, "encrypt_data(JLjava/lang/String;J)Ljava/lang/String;" , 0 , "63122cb8da575c7e71ace0dccb1e35b0f2a43758user/login18888888888ET4Sdfjs+/VkbhxXerUBFg==" , 85 ); String result = stringObject.getValue(); System.out.println("result=" + result); } public static void main (String[] args) { new MainActivity ().encrypt_data(); } }
在安卓项目中调用别人的so中的函数 新建安卓项目 在app目录下新建一个libs目录,把libCrypt.so放到libs目录下
在build.gradle.kts中的android里添加如下内容
1 2 3 4 5 sourceSets { getByName("main") { jniLibs.srcDirs("libs") } }
新建一个包,包名必须要与方法所在的包一致,com.yoloho.libcore.util
新建一个类,类名也要与方法所在的类一致,DayimaPrivateUtil
添加代码
1 2 3 4 5 6 7 8 9 package com.yoloho.libcore.util;public class Crypt { static { System.loadLibrary("Crypt" ); } public static native String encrypt_data (long j2, String str, long j3) ; }
调用
1 2 3 String sign = Crypt.encrypt_data(0 ,"63122cb8da575c7e71ace0dccb1e35b0f2a43758user/login18888888888ET4Sdfjs+/VkbhxXerUBFg==" ,85 );TextView tv = findViewById(R.id.textview);tv.setText(sign);
效果