安卓逆向 某开门软件签名算法、蓝牙开门、密码开门分析

admin 2025年6月7日10:04:24评论0 views字数 7166阅读23分53秒阅读模式

0x00 前言

工具:burp、frida、ida

难点:Flutter

小区在很早之前就换了某科技的门禁系统

支持手机开门、门卡开门、人脸开门

所有信息均已脱敏和打马赛克

手机开门软件及其逆天,广告满天飞,一气之下就逆了

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

0x01 准备

获取安装包

下载最新版发现加壳了,是 蛮犀加固企业壳

很久之前给这软件写过去广告模块,所以已知旧版本是无壳的

所以只用往回找没壳的版本,就能使用 Frida Hook 了

SSL 验证

使用代{过}{滤}理抓包发现无法抓到,并且有 SSL 验证

使用 IDA 打开 libflutter.so

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

搜索 ssl_client

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

获取到 SSL 验证 函数为 0x3E0F74

libapp.so 逆向

使用 Blutter 一把梭哈

把脚本导入 IDA

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

可以看到函数名也全部都还原了

0x02 抓包分析

初始化抓包

导出 Burp 的 SSL 证书,并存到 /data/local/tmp/cert-der.crt

使用 Blutter 提供的 frida 脚本

 复制代码 隐藏代码/*   Android SSL Re-pinning frida script v0.2 030417-pier$ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt   $ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pausehttps://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/   UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 !*/setTimeout(function () {  Java.perform(function () {    console.log("");    console.log("[.] Cert Pinning Bypass/Re-Pinning");    var CertificateFactory = Java.use("java.security.cert.CertificateFactory");    var FileInputStream = Java.use("java.io.FileInputStream");    var BufferedInputStream = Java.use("java.io.BufferedInputStream");    var X509Certificate = Java.use("java.security.cert.X509Certificate");    var KeyStore = Java.use("java.security.KeyStore");    var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");    var SSLContext = Java.use("javax.net.ssl.SSLContext");    // Load CAs from an InputStream    console.log("[+] Loading our CA...");    var cf = CertificateFactory.getInstance("X.509");    try {      var fileInputStream = FileInputStream.$new(        "/data/local/tmp/cert-der.crt"      );    } catch (err) {      console.log("[o] " + err);    }    var bufferedInputStream = BufferedInputStream.$new(fileInputStream);    var ca = cf.generateCertificate(bufferedInputStream);    bufferedInputStream.close();    var certInfo = Java.cast(ca, X509Certificate);    console.log("[o] Our CA Info: " + certInfo.getSubjectDN());    // Create a KeyStore containing our trusted CAs    console.log("[+] Creating a KeyStore for our CA...");    var keyStoreType = KeyStore.getDefaultType();    var keyStore = KeyStore.getInstance(keyStoreType);    keyStore.load(nullnull);    keyStore.setCertificateEntry("ca", ca);    // Create a TrustManager that trusts the CAs in our KeyStore    console.log(      "[+] Creating a TrustManager that trusts the CA in our KeyStore..."    );    var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();    var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);    tmf.init(keyStore);    console.log("[+] Our TrustManager is ready...");    console.log("[+] Hijacking SSLContext methods now...");    console.log("[-] Waiting for the app to invoke SSLContext.init()...");    SSLContext.init.overload(      "[Ljavax.net.ssl.KeyManager;",      "[Ljavax.net.ssl.TrustManager;",      "java.security.SecureRandom"    ).implementation = function (a, b, c) {      console.log("[o] App invoked javax.net.ssl.SSLContext.init...");      SSLContext.init        .overload(          "[Ljavax.net.ssl.KeyManager;",          "[Ljavax.net.ssl.TrustManager;",          "java.security.SecureRandom"        )        .call(this, a, tmf.getTrustManagers(), c);      console.log("[+] SSLContext initialized with our custom TrustManager!");    };}, 0);var libflutter = null;function onLibFlutterLoaded() {  const ssl_pinning_addr = 0x3e0f74;  Interceptor.attach(libflutter.add(ssl_pinning_addr), {    onEnterfunction (args) {      console.log("Disabling SSL validation");    },    onLeavefunction (retval) {      console.log("Retval: " + retval);      retval.replace(0x1);    },  });}function tryLoadLibFlutter() {  libflutter = Module.findBaseAddress("libflutter.so");  if (libflutter === nullsetTimeout(tryLoadLibFlutter, 500);  else onLibFlutterLoaded();}tryLoadLibFlutter();

加上 Bypass SSL 验证

开始抓包

禁止检查更新

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

将 /v1/appVersion 地址替换为空即可绕过更新检查

发送验证码

Host: aHR0cHM6Ly9nYXRld2F5Mi5xaW5saW5rZWppLmNvbQ==

URL: /member/sms/sendSecurityCode

Header

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

Param

{  "mobile": "" // 手机号}

数据解释

Header

下列头必须添加

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

Param

下面为签名所需数据

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

登录

Host: bW9iaWxlYXBpMy5xaW5saW5rZWppLmNvbQ==

URL: /api/app/v1/login

Param

{  "smsCode": "", // 验证码  "mobile": "", // 加密手机号  "sign": "", // 签名  "version": "4.9.9", // 软件版本  "nonce": "", // 随机数  "appChannel": 1, // 未知 软件渠道?  "timestamp": 0 // 时间戳}

检查登录

Host: bW9iaWxlYXBpMy5xaW5saW5rZWppLmNvbQ==

URL: /api/app/v1/checkLogin?sessionId=令牌

Param

{  "method": "checkLogin", // 调用方法  "sign": "", // 签名  "version": "4.9.9", // 软件版本  "nonce": "0", // 随机数  "timestamp": 0 // 时间戳}

获取所有门

Host: bW9iaWxlYXBpMy5xaW5saW5rZWppLmNvbQ==

URL: /api/app/user/v2/queryAllUserDoorByCache?sessionId=令牌

Param

类型为 form data

communityId社区 ID

开门

Host: bW9iaWxlYXBpMy5xaW5saW5rZWppLmNvbQ==

URL: /api/open/doorcontrol/v2/open?sessionId=令牌

Param

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析
安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

签名分析

function functionCallLog(  address,  argsLength = 1,  func_name = "") {  Interceptor.attach(libapp.add(address), {    onEnterfunction () {      init(this.context);      for (let i = 0; i < argsLength; i++) {        try {          const arg = getArg(this.context, i);          const [tptr, cls, values] = getTaggedObjectValue(arg);          this.info = this.info || [];          this.info.push(            `(${i}${cls.name}@${tptr.toString().slice(2)} = ${JSON.stringify(              values,              null,              2            )}`          );        } catch (e) {          this.info = this.info || [];          this.info.push(`(${i}${e}`);        }      }    },    onLeavefunction (result) {      if (this.skip) {        return;      }      const [tptr, cls, values] = getTaggedObjectValue(result);      var stamp = new Date().getTime() + 8 * 60 * 60 * 1000;      var beijingTime = new Date(stamp).toISOString();      let debug_info = [        `Time: ${beijingTime}`,        `Function name: ${func_name || "unknown"}`,        `Function ptr: 0x${address.toString(16)}`,        `Args:`,        this.info.join("n"),        `Return:`,        `${cls.name}@${tptr.toString().slice(2)} = ${JSON.stringify(values, null2)}`,        "RegisterNatives called from:n" +          Thread.backtrace(this.contextBacktracer.ACCURATE)            .map(DebugSymbol.fromAddress)            .join("n")      ];      console.log("");      console.log("==========================");      console.log(debug_info.join("n"));      console.log("==========================");      console.log("");    },  });}

添加一个辅助函数

API 签名分析

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

根据关键词找到 qinlin_utils_Encryption_Encryption::getHeaderSign_c52af0 函数

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

查看函数发现调用了 common_base_service_task_qapp_service_task__QappEncrypt::md5Encrypt_65fce0 函数

function onLibappLoaded() {    functionCallLog(0x65fce01"QappEncrypt::md5Encrypt");    functionCallLog(0xc52af02"Encryption::getHeaderSign");}

把这两个函数都 Hook 了

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

可以看到签名生成是请求参数排序后添加 &key=qiAnlPinP 使用 MD5 生成的签名

发送验证码签名分析

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

可以看到签名生成是请求参数排序后添加 &appsecret=OKFoQ9MmNXQtcyXROo4PnaFkfDPTHuDg 使用 MD5 生成的签名

登录分析

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

通过关键词搜索可以发现有 AES 加密,猜测手机号加密是使用的 AES

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

Hook 后也是获取到了加密密钥 FBC213C4C7BEEBD2AA4EDBF0F681C41B

0x03 密码和蓝牙开门

密码开门

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析
安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

搜索关键词后找到获取开门密码的函数

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

第一个参数 社区ID

第二个参数 设备MAC地址

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

查看伪代码,发现是把一串文本传入获取MD5然后进行一些操作后获取密钥,分别Hook这两个函数

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析
安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

可以发现生成是使用的

md5(MAC地址 + 当前时间整分时间戳 + 社区ID)

然后替换全部 a-z 字符,取后4位作为密码

注:整分是指 当前时间 2025-01-19 18:15:58 整分 2025-01-19 18:10:00

蓝牙开门

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

使用 Xposed Hook 蓝牙读写函数,发现只有写入,没有数据返回,因此可以判定,蓝牙开门只向门发送数据而不校验是否开门成功

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

使用的服务是 0xFFF0

使用的特征是 0xFFF1

使用 ESP32 模拟蓝牙

使用官方的 BLE server multiconnect 改改就行

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

设置服务UUID

设置特征UUID

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

设置蓝牙名称

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

数据接收处理

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

添加一个替换,把在线替换成离线

让设备离线,使用蓝牙开门而不使用网络开门

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

由于 ESP32 无法设置MAC地址为组播地址,需要在Burp中添加一个替换规则

把设备MAC地址替换成ESP32的地址

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

可以看到已经模拟成功

数据分析

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

搜索关键词找到蓝牙连接处理函数

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析
安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

可以发现,使用了3des进行加密

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

经过几次对比发现数据和密钥都是动态生成的

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

密钥生成 设备MAC地址 + 年份 + 月份 + 日期 + 小时 + 分钟 + 55AA5A5AA5

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

数据生成 年份 + 月份 + 日期 + 小时 + 分钟 + 设备名后6位

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

蓝牙发送数据

截取 3DES 加密返回的前16位

拼接发送数据,具体如下 固定前缀(30) + 前16位数据 + 年份 + 月份 + 日期 + 小时 + 分钟 + 固定后缀(FA34DD0001) + 校验码(xor校验)

其他

如何确认是固定数据的

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析
安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

在反编译出来的 Dart 机器码中可以直接搜索到

0x04 结语

至此,所有常用功能均已逆向完毕,需要相关源码的请自行到 Github 搜索

· 今 日 推 荐 ·

安卓逆向  某开门软件签名算法、蓝牙开门、密码开门分析

本文内容来自网络,如有侵权请联系删除

原文始发于微信公众号(逆向有你):安卓逆向 -- 某开门软件签名算法、蓝牙开门、密码开门分析

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

发表评论

匿名网友 填写信息