一
需要准备的工具和环境
1、apktool_2.4.1.jar--用于反编和打包apk
反编指令java -jar apktool_2.4.1.jar d .test.apk -o test打包指令java -jar apktool_2.4.1.jar b test -o testPack.apk
2、jadx-gui-1.4.5--用于查看 apk 反编译源码,方便快速导出查找
3、Linux环境安装 zipalign 、apksigner--用于对齐和重新签名
二
坑位记录
1、反编后,啥也不改,直接再打包回 apk,z4 对齐并签名后安装失败,错误原因为:
D NativeLibraryHelper: Library 'libsgmain.so' is not page-aligned - will not be able to open it directly from apk.W NativeHelper: Failure copying native libraries [errorCode=-2]
提示 so 未对齐,试了很多方法一直不都行,后来验证了下发现这货根本不是so,而是 jar 文件,这一招正是为了防止反编的手段。
file libsgmain.so: Java archive data (JAR)
解决办法
将重打包后的 apk 再解压开,强制 so 不压缩,然后再进行对齐和签名即可,完整指令步骤如下
java -jar apktool_2.4.1.jar b .test -o unsigned.apk然后再到 linux 环境下处理unzip unsigned.apk -d apk_unpacked/cd apk_unpacked/zip -r -0 ../repack-unsigned.apk *zipalign -f -v -p 4 repack-unsigned.apk aligned.apkapksigner sign --ks test.jks --ks-key-alias xxxx -ks-pass pass:xxxx ./aligned.apk
最终得到的 aligned.apk 就可以成功安装。
三
开始分析
先将 apk 通过 apktool 反编出来备用,一会方便修改 smali 代码
然后在 jadx-gui 中打开 apk,打开工程后,点击文件保存将所有代码保存出来,然后 cp 到 linux 下,这样方便快速查找关键词。
比如界面显示未授权的设备,可以通过 grep 未授权 ./ -nir 快速搜索到具体位置,然后在 jadx-gui 中快速定位代码位置。
1、未授权设备弹框检查流程如下
1、com.a.a.a.a.a.booleanValue() 为 false 则直接忽略检查,可能是为了测试模式留的
2、为 true,则检查指定包名的apk对应的 SHA-256 签名值是否和固定值相等,一致则认为设备已授权
private void c() {if (com.xpay.watch.utils.a.a(this)) { getWindow().addFlags(128); setContentView(com.xpay.watch.utils.g.a(this, R.layout.activity_watch_main)); com.xpay.android.watchsdk.g.a().g();if (!com.xpay.android.watchsdk.l.a().e()) { com.xpay.android.watchsdk.g.a().a(com.xpay.watch.utils.h.a(), com.xpay.watch.utils.h.b(), null);try { com.xpay.android.watchsdk.ble.d.a().f(); } catch (Throwable th) { com.xpay.android.phone.inside.log.api.b.a(th); }this.a.postDelayed(this.h, 200L); com.xpay.android.watchsdk.l.a().l(); } else { d();this.a.postDelayed(this.i, 200L); } } else { DialogUtil.aaVar =new DialogUtil.a(this); aVar.a(DialogUtil.DialogType.SINGLE_BUTTON); aVar.a(getString(R.string.dlg_authorization_check_failed_title)); aVar.b(getString(R.string.dlg_authorization_check_failed_tips)); aVar.c("c-" + com.xpay.watch.utils.h.a() + " m-" + Build.MODEL); aVar.a(new h(this)); DialogUtil.a(aVar); }public static booleana(Context context) {if (com.a.a.a.a.a.booleanValue()) { String[] split = h.f().split(","); String[] split2 = h.e().split(",");if (split2 == null || split2.length <= 0) {return false; }for (inti =0; i < split2.length; i++) { Log.w("xpay_watch", "packageName |" + split2[i]);if (a(context, split2[i], split)) {return true; } }return false; }return true; }private static booleana(Context context, String str, String[] strArr) { List<String> a2 = a(context, str);if (a2 == null || a2.size() <= 0) {return false; }for (String str2 : strArr) {Stringtrim = str2.trim(); Log.w("xpay_watch", str + " " + a2.get(0).toUpperCase() + trim.toUpperCase());if (TextUtils.equals(a2.get(0).toUpperCase(), trim.toUpperCase())) {return true; } }return false; }private static List<String> a(Context context, String str) {try {PackageInfopackageInfo = context.getPackageManager().getPackageInfo(str, 64);if (packageInfo != null) { Signature[] signatureArr = packageInfo.signatures;MessageDigestmessageDigest = MessageDigest.getInstance("SHA-256");ArrayListarrayList =new ArrayList();for (Signature signature : signatureArr) {byte[] digest = messageDigest.digest(signature.toByteArray());StringBuildersb =new StringBuilder((digest.length * 3) - 1);intlength = digest.length - 1;for (inti =0; i <= length; i++) {byteb = digest[i]; sb.append(a[(b & 240) >> 4]); sb.append(a[b & 15]);if (i < length) { sb.append(':'); } }Stringsb2 = sb.toString(); arrayList.add(sb2); com.xpay.android.phone.inside.log.api.b.a("package |" + str + "| fingerprint: " + sb2); Log.w("xpay_watch", "package |" + str + "| fingerprint: " + sb2); }return arrayList; }return null; } catch (Throwable th) { th.printStackTrace();return null; } }
2、获取设备蓝牙mac流程如下
看到蓝牙绑定时报错 log 如下
14:05:57.870 9853 BluetoothGattServer D onServiceAdded() - handle=40 uuid=00003802-0000-1000-8000-00805f9b34fb status=014:05:57.874 9853 BluetoothGattServer W Unhandled exception in callback java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.String.toUpperCase()' on a null object reference at com.xpay.android.watchsdk.ble.d.b(SourceFile:542) at com.xpay.android.watchsdk.ble.d.a(SourceFile:44) at com.xpay.android.watchsdk.ble.d$a.onServiceAdded(SourceFile:441) at android.bluetooth.BluetoothGattServer$1.onServiceAdded(BluetoothGattServer.java:136) at android.bluetooth.IBluetoothGattServerCallback$Stub.onTransact(IBluetoothGattServerCallback.java:85) at android.os.Binder.execTransact(Binder.java:723)14:06:13.725 9853 zygote I Debugger is no longer active
对应代码为先获取 BluetoothManager getAdapter 通过 getAddress 方法获取 mac,
取不到则再进行反射调用,调用时抛出权限异常
Caused by: java.lang.SecurityException: Need LOCAL_MAC_ADDRESS permission: Neither user 10032 nor current process has android.permission.LOCAL_MAC_ADDRESS.
高版本安卓此权限必须是系统 app 才能拥有,可以修改 smali 代码直接走下面的 Secure 查询 bluetooth_address 即可。
public String b() {if (this.k == null) {if (this.h == null) {this.h = (BluetoothManager) this.c.getSystemService("bluetooth");if (this.h == null) { com.xpay.android.phone.inside.log.api.b.b("Unable to initialize BluetoothManager.");return null; } }this.i = this.h.getAdapter();if (this.i == null) { com.xpay.android.phone.inside.log.api.b.b("Unable to obtain a BluetoothAdapter.");return null; }this.k = this.i.getAddress();if (TextUtils.isEmpty(this.k) || this.k.endsWith("00:00:00:00:00")) {try {this.k = a(this.i); } catch (Throwable th) { com.xpay.android.phone.inside.log.api.b.a(th); } }if (TextUtils.isEmpty(this.k) || this.k.endsWith("00:00:00:00:00")) {try {this.k = Settings.Secure.getString(this.c.getContentResolver(), "bluetooth_address"); } catch (Throwable th2) { com.xpay.android.phone.inside.log.api.b.a(th2); } } com.xpay.android.phone.inside.log.api.b.a("ServerAddress:" + this.k); }return this.k; }private static String a(BluetoothAdapter bluetoothAdapter) { Object obj;try { Field declaredField = BluetoothAdapter.class.getDeclaredField("mService"); declaredField.setAccessible(true); obj = declaredField.get(bluetoothAdapter); } catch (Throwable th) { com.xpay.android.phone.inside.log.api.b.a(th); }if (obj == null) {return null; } Method declaredMethod = obj.getClass().getDeclaredMethod("getAddress", new Class[0]); declaredMethod.setAccessible(true); Object invoke = declaredMethod.invoke(obj, new Object[0]);if (invoke != null && (invoke instanceof String)) {return (String) invoke; }return null; }
扫码绑定后本地开始生成付款条码
public void onResume() {super.onResume();b(this.h); com.xpay.android.watchsdk.l.a().l();//解绑if (com.xpay.android.watchsdk.l.a().e()) {this.i.postDelayed(new q(this), 50L); } }public boolean e() {try {BarcodePay.a();if (com.xpay.security.wear.barcode.b.a() != null) {//本地xpayUserInfo.json是否存在if (!TextUtils.isEmpty(BarcodePay.b())) {//so computeBarcodeString 请求return true; } } } catch (Throwable th) { com.xpay.android.phone.inside.log.api.b.a("isBounded", th); }try {return !TextUtils.isEmpty(a((String) null, true));//网络激活 } catch (Throwable th2) { com.xpay.android.phone.inside.log.api.b.a("isBounded", th2);return false; } }private String a(String str, boolean z) throws SecException {try {if (com.xpay.security.wear.barcode.b.b() != null) {String b = BarcodePay.b();if (!TextUtils.isEmpty(b)) {return b; } } } catch (Throwable th) { com.xpay.android.phone.inside.log.api.b.a(th); }IDynamicDataStoreComponent dynamicDataStoreComp = SecurityGuardManager.getInstance(this.a).getDynamicDataStoreComp();if (dynamicDataStoreComp.getBoolean("success")) { long j = 0;try { j = Long.valueOf(dynamicDataStoreComp.getString("time_diff")).longValue(); } catch (Throwable th2) { com.xpay.android.phone.inside.log.api.b.a(th2); }String valueOf = String.valueOf(j + (System.currentTimeMillis() / 1000));String str2 = new String(SecurityGuardManager.getInstance(this.a).getSafeTokenComp().getOtp("otp_token", 0, TextUtils.isEmpty(str) ? new String[]{valueOf, "0"} : new String[]{valueOf, "0", str}, null));if (!z) {this.b = str2;return str2; }return str2; }return null; }
调用对应 so 生成条码
public class BarcodePay {public static native String computeBarcodeString(String str, long j, String str2);public static native longcomputeMovingFactor(long j, int i, long j2);public static native String getSeedString(String str);public static native intstore(byte[] bArr, int i, byte[] bArr2, int i2, String str);public static void a() { }static { System.loadLibrary("xpaywear"); }public static String b() {Stringstr =null;try {JSONObjectjSONObject =new JSONObject(getSeedString(WatchApplication.a().getFilesDir() + File.separator));inti = jSONObject.getInt("interval"); str = computeBarcodeString(jSONObject.getString("otp"), computeMovingFactor(b.b().d(), i, System.currentTimeMillis() / 1000), jSONObject.getString("index")); Log.v("WatchBarcode", "barcodeString:" + str); } catch (NullPointerException e) { Log.e("WatchBarcode", "NullPointerException"); } catch (JSONException e2) { Log.e("WatchBarcode", "JsonException"); }if (TextUtils.isEmpty(str)) {try { b.c(); } catch (Throwable th) { com.xpay.android.phone.inside.log.api.b.a(th); } }return str; }}
四
免责声明
本文内容仅用于技术研究与学习交流目的,不涉及任何商业用途。
文中所提及的技术手段、工具使用及分析过程均基于开源或公开信息进行探讨,不代表鼓励或支持任何非法行为。
若您使用相关技术从事违法行为,后果自负,与本文作者无关。
如有侵权,联系删文。
看雪ID:cczheng
https://bbs.kanxue.com/user-home-939383.htm
#
原文始发于微信公众号(看雪学苑):关于手表版本 apk 逆向分析过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论