关于手表版本 apk 逆向分析过程

admin 2025年6月24日23:32:20评论8 views字数 9849阅读32分49秒阅读模式

需要准备的工具和环境

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((Stringnulltrue));//网络激活        } 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"0TextUtils.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;    }}

免责声明

本文内容仅用于技术研究与学习交流目的,不涉及任何商业用途。

文中所提及的技术手段、工具使用及分析过程均基于开源或公开信息进行探讨,不代表鼓励或支持任何非法行为。

若您使用相关技术从事违法行为,后果自负,与本文作者无关。

如有侵权,联系删文。

关于手表版本 apk 逆向分析过程

看雪ID:cczheng

https://bbs.kanxue.com/user-home-939383.htm

*本文为看雪论坛优秀文章,由 cczheng原创,转载请注明来自看雪社区
关于手表版本 apk 逆向分析过程
议题征集中!看雪·第九届安全开发者峰会

#

原文始发于微信公众号(看雪学苑):关于手表版本 apk 逆向分析过程

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月24日23:32:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   关于手表版本 apk 逆向分析过程https://cn-sec.com/archives/4195815.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息