小米运动APP的坎坷抓包之旅
0x00
今天在研究小米手环3 NFC版的模拟Mifare卡功能,发现模拟出的卡0扇区是只读的,遂在网上搜索操作0扇区方法。
经过一番搜索 发现了这位老哥的一篇文章《小米手环3 NFC 自定义 门禁卡数据》。仔细阅读了一下,发现这位老哥的思路可行。当我架好了Fiddler,安卓端设置好代理,安装好Fiddler的根证书,开始抓包的时候却发现Fiddler并不能解密,而且小米运动app也无法正常通信。尝试用浏览器开了个网页检查了下证书,发现Fiddler证书配置正确,并且可以正常抓包。猜测是App设置了只信任系统中的证书。
无奈,只好对APK方面进行动手。
提示:图片看不清可以选择右键在新标签页中查看
0x01
首先准备APK反编常用工具一套:apktool, baksmali, dex2jar, JD-GUI
另外需要准备APK重打包签名工具jarsigner
(JDK自带)
准备一个小米运动APP:com.xiaomi.hm.health.apk
0x02
首先用apktool将APK的classes*.dex逆向为smali文件
apktool -r d ./android-async-http-1.4.8.jar
由于我们不需要解开资源文件所以这里加了-r
开关,不解开资源文件可以避免打包时的一些错误,并且可以加快打包速度。
然后使用d2j-dex2jar将classes*.dex转换成jar,这样可以方便我们使用JD-GUI工具查看源码。
将转换后的jar拖进JD-GUI,顿时头皮一麻,几乎所有业务类、方法、变量名全部被混淆成了oO0的组合。
思考了一下,想到刚刚用Fiddler抓包时app无法正常通信,是否可能会吐出异常日志呢?或许我可以从logcat中的报错找到对应方法。
0x03
想到这里,将手机连到电脑,启用开发者模式,使用adb shell ps
查找到包名为com.xiaomi.hm.health程序的PID。用adb logcat --pid=查找到的PID
来监视app进程的日志输出。
随便在软件上操作一下,果然出现报错堆栈信息,根据报错信息我从上从下查下去,抛除内置库,发现这个app使用了一个名为com.loopj.android.http的库。
0x04
根据以往开发经验,这种库中应该有可以关闭证书校验的开关。在JD-GUI中可以看到这个库并没有被混淆。但由于编译后代码没有注释通常难以理解。经过一番搜索,找到了该库的源码:android-async-http。在AsyncHttpClient类第175行中,发现这个类的构造方法中fixNoHttpResponseException参数可以省略ssl验证。
0x05
看到这里,我就准备着手修改代码了。定位到使用Apktool逆向的文件中,使用编辑器打开AsyncHttpClient.smali,由于我以前从未接触过smali,打开文件的一瞬间还是有点头大。但还好,这东西虽然类似汇编,但可读性不知道比汇编高到哪里去了,通过简单的阅读,找到了构造函数的位置。
结构层次一目了然,其中可以观察到省略ssl参数默认值为false。在smali文件中表现为const/4 v0, 0x0
。可以推测出最后的0x0代表false,按照经验0x1应该就是代表true了。逐个修改完后保存文件,使用Apktool将smali文件打包回apk。
apktool b --use-aapt2 ./com.xiaomi.hm.health -o ./miapp.apk
打包出apk,然后使用对这个apk进行签名,由于之前搞过两天安卓开发,所以我已经有准备好的密钥文件了。没有可以到网上搜一搜生成方法。
jarsigner -verbose -keystore <密钥存储文件.jks> -signedjar <签名后的apk输出路径> <待签名的apk路径> <对应密钥名>
将签名后的apk安装到手机前,记得卸载原来的apk,因为我没有修改apk包名,原签名与修改后重打包的签名不一致会导致无法安装。启动app正常。当我充满希望再次抓包的时候,现实再次给了我重重一击。Fiddler还是无法解密,App也没能绕过ssl检查进行正常通信。
0x06
我再次仔细的对比代码,结合Loopj项目历史Issues关于SSL的问题,发现小米运动app中的loopj库虽然有开关可以控制ssl检查,但实际上缺少了绕过ssl检查的相关流程的代码,再次查阅原库。在第306行中找到了消失的绕过ssl检查代码。这里调用了一个MySSLSocketFactory类,也是app中缺少的。由于经验缺乏,我选择了比较绕的方法来解决问题。先下载了一个老发行版。下载下是jar格式,里面类也都是class文件,并不是smali文件,那就开始转吧。
用d2j-jar2dex工具将jar转换为dex
d2j-jar2dex.sh --output=loopj.dex android-async-http-1.4.8.jar
然后使用baksmali工具再将dex拆成smali
java -jar ./baksmali-2.3.4.jar d ./loopj.dex
将其中的MySSLSocketFactory$1.smali
和MySSLSocketFactory.smali
文件复制到对应位置。
这里我还踩了个坑,用了d2j-dex2smali来拆loopj.dex,结果是复制过去,修改完重打包时apktool不能正常识别d2j-dex2smali吐出的smali,会报错。
0x07
再次返回JD-GUI,对照smali代码直接将SSLSocketFactory部分改成MySSLSocketFactory。
0x08
再次保存、打包、签名安装。打开app,使用Fiddler抓包。
抓包解密成功。但是门卡模拟还是进不去,该部分请求仍然无法解密。查看日志,并没有直接输出什么错误,未完待续吧。
LogCat记录一下
01-13 03:17:30.340 27289 27588 I Logcat : api --> getCardList() >>> (ApiWrapper.kt:34) -> invoke() | Thread:RxCachedThreadScheduler-4
01-13 03:17:30.349 27289 27588 I Logcat : api --> O00000Oo(374cf6c517d5) >>> (ApiWrapper.kt:34) -> invoke() | Thread:RxCachedThreadScheduler-4
01-13 03:17:30.354 27289 27588 I Logcat : api <-- O00000Oo()【执行成功】, 耗时: 1 ms >>> (ApiWrapper.kt:43) -> invoke() | Thread:RxCachedThreadScheduler-4
2020/01/14
后来发现小米运动app门卡请求部分调用的是okhttp3库,研究了半天,又搓了一个MyX509TrustManager
package okhttp3.internal;
import java.security.cert.CertificateException;
import javax.net.ssl.X509TrustManager;
public class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
编译成smali,放在okhttp3\internal下面 与Util.smali同级,然后修改Util.smali,将原TrustManaver修改成我们自己的MyX509TrustManager
.line 667
invoke-virtual {v0}, Ljavax/net/ssl/TrustManagerFactory;->getTrustManagers()[Ljavax/net/ssl/TrustManager;
move-result-object v0
//修改为
.line 667
const/4 v1, 0x1
new-array v0, v1, [Ljavax/net/ssl/TrustManager;
const/4 v1, 0x0
new-instance v2, Lokhttp3/internal/MyX509TrustManager;
invoke-direct {v2}, Lokhttp3/internal/MyX509TrustManager;-><init>()V
aput-object v2, v0, v1
这样就完成了对okhttp ssl检查的绕过,再使用fiddler进行抓包,这次可以成功截获门卡部分请求的api。
0xFF
遗憾的是 新版本的api已经变更为服务器下发command执行,其中似乎指令数据还没有研究明白。
已有 4 条评论
2020年01月17日
小撸厉害啊!
2020年09月11日
返回的指令是ADPU指令,可以通过某些网站解析它的含义,但是还是会有一部分解析不出来。
2021年06月06日
请问登录的时候也会进行加密,这部分应该怎么解决呢
2021年06月07日
时间有点久远了,如果正确干掉所有证书强验证,应该是可以正常登录的