前言
某天收到一个测试安卓app的项目,因此简单的看了一看。
app 测试
先从项目群下载该app
判断是否有壳
这里借助工具PKID去查询,提示可能是无壳或者未知的加壳方式
PDID查壳工具地址:http://www.legendsec.org/1888.html
实际上基本也就无壳了,直接丢入jadx了。
当然这里也可以手工判断有无加壳。
可参考文章:https://blog.csdn.net/g5703129/article/details/85054405
反编译
这里就直接借助jadx工具去查看源码了
jadx工具地址:https://github.com/skylot/jadx
APP运行和代理配置
这里使用夜神模拟器去运行app
夜神模拟器官网地址:https://www.yeshen.com/
直接将该apk文件拖入到模拟器里面,即可安装成功
开始代理配置
点击工具
,在设置
里面选择WLAN
鼠标左键长按点击WiredSSID
,再点击修改网络
展开高级选项
、代理选择手动
输入物理机IP地址,以及监听端口。如我本机是192.168.1.104,监听端口是8080
然后在抓包工具burpsuite
处设置监听地址和端口
直接打开APP,发现数据已经做了传输加密处理,不方便渗透
请求数据加密信息搜集
此处可能被加密的参数主要如下:
参数名 | 值 |
---|---|
deviceId | a2d2ea2c0014c3b6 |
appKey | sdfskfjdsklewkrw |
knockToken | 7e3369d289f52c7800d251e849d5b3bb |
requestId | 4158ad70433b4bfa84c0a793fb974e88 |
sign | 304502206a09cc568befa5c2748ac3fc853e8cf39ba864f8a1a829b8ec23b b6c91a549090221008eccaf8e668b2c0b4ff76949159c78ab515627a0185 1e25df1ec9320b1822e30 |
key | 045b6afabdd0519e757872f42fb976c4f3aff7357ceff2254d428112196942 91931e5a11dc44d00090226675aa0052ab3fd1ad33cbcb39f42645be5cb5 90b9e7d2156b9ed183e851f1cf5cf46b1486aa2eca40eb37ab0fcf8da506a6 de7e5bc166bf212f14465df1c9009b5d078bdd6a51 |
token | 7e3369d289f52c7800d251e849d5b3bb |
请求体数据 | 847216a2b3da8ce1544a73424b14f194228c487063895896ef029c3c1394 ca3ff7604e71a0990b01869c616f0a359262c9e7649ccc7ee48c99f106350d 0c15e2be511bbc240696aefe09e711bc1b0c73 |
数据加密流程分析
AndroidManifest.xml 分析
通过jadx
查看apk文件资源文件目录下的AndroidManifest.xml
文件
简单解释一下该xml文件的内容
app 权限声明
uses-permission节点
uses-permission节点 | 代表权限 |
---|---|
android:name=”android.permission.ACCESS_COARSE_LOCATION” | 允许应用程序访问设备的粗略位置信息 |
android:name=”android.permission.ACCESS_FINE_LOCATION” | 允许应用程序访问设备的精确位置信息 |
android:name=”android.permission.VIBRATE” | 允许应用程序控制设备的振动器 |
android:name=”android.permission.REQUEST_INSTALL_PACKAGES” | 允许应用程序请求安装包的安装权限 |
android:name=”android.permission.ACCESS_DOWNLOAD_MANAGER” | 允许应用程序访问系统的下载管理器 |
android:name=”android.permission.WRITE_EXTERNAL_STORAGE” | 允许应用程序写入外部存储器 |
android:name=”com.android.launcher.permission.INSTALL_SHORTCUT” | 允许应用程序在主屏幕上安装快捷方式 |
android:name=”com.android.launcher.permission.READ_SETTINGS” | 允许应用程序读取主屏幕设置信息 |
android:name=”android.permission.READ_EXTERNAL_STORAGE” | 允许应用程序读取外部存储器上的文件 |
android:name=”android.permission.INTERNET” | 允许应用程序访问网络 |
android:name=”android.permission.ACCESS_NETWORK_STATE” | 允许程序可以获得设备的网络状态信息 |
android:name=”android.permission.MANAGE_EXTERNAL_STORAGE” | 允许应用程序管理外部存储器 |
android:name=”android.permission.CAMERA” | 允许应用程序访问设备的摄像头 |
android:name=”android.permission.ACCESS_WIFI_STATE” | 允许应用程序访问设备的 Wi-Fi 状态信息 |
android:name=”android.permission.CHANGE_WIFI_STATE” | 允许应用程序更改设备的 Wi-Fi 状态 |
android:name=”android.permission.READ_PHONE_STATE” | 允许应用程序读取设备的电话状态和身份识别码 |
android:name=”android.permission.USE_BIOMETRIC” | 允许应用程序使用生物识别技术来验证用户身份 |
android:name=”android.permission.FLASHLIGHT” | 允许应用程序控制设备的闪光灯 |
android:name=”android.permission.GET_TASKS” | 允许应用程序获取正在运行的任务信息 |
uses-feature 节点
uses-feature | 功能 |
---|---|
android:name=”android.hardware.camera.autofocus” | 相机自动对焦功能 |
android:name=”android.hardware.camera” android:required=”false” | 允许应用程序访问设备的摄像头。 |
android:name=”android.hardware.camera.flash” android:required=”false” | 允许应用程序使用设备的闪光灯 |
android:name=”android.hardware.screen.portrait” | 声明应用程序支持竖屏模式 |
android:name=”android.hardware.camera.front” android:required=”false” | 允许应用程序访问设备的前置摄像头 |
android:name=”android.hardware.screen.landscape” android:required=”false” | 声明应用程序支持横屏模式 |
android:name=”android.hardware.wifi” android:required=”false” | 允许应用程序访问设备的 Wi-Fi 功能 |
application节点
application
里面设置了应用程序的类名android:name=com.xxx.base.xxx.app.App
,作用是在应用程序启动时执行初始化和设置全局变量等操作。
Activity子节点
注:以下Activity的功能仅从变量命名去猜测功能,且一个Activity代表一个UI界面或者一个特定的功能
Activity节点 | 功能 |
---|---|
AuthActivity | 用户认证登录的Activity |
ScanSelfActivity | 扫描自己的二维码的Activity,设置启动模式为singleTask |
ScanQrCodeZxingActivity | 扫描二维码 |
ShowFrgActivity | |
TestActivity | 测试用的Activity |
WelcomActivity | 应用程序的欢迎页面,由于设置了android.intent.action.MAIN,因此程序启动时,先启动WelcomActivity |
IndexActivity | 应用程序主界面的Activity,设置启动模式为singleTask |
LoginMainActivity | 登录界面的Activity |
MainActivity | 应用程序主界面的Activity |
SelectPicPopupWindow | 选择图片的弹窗活动 |
CaptureActivity | 扫描二维码的Activity |
ToCheckLivingActivity | 可能是生物识别有关的Activity |
CheckLivingActivity | 可能是生物识别有关的Activity |
通过分析发现程序打开时,最先运行WelcomActivity
,因此鼠标双击点入跟进分析。
程序执行分析
WelcomActivity
为了方便理解,这里借助该博客https://blog.csdn.net/weixin_44235109/article/details/107600938里的的activity生命周期的示意图。
当程序被打开时,最先执行onCreate
方法
在此处的WelcomActivity
执行后,最先执行onCreate方法。并最终根据knockResultEvent.isConnect
的布尔值分别进入到goLoginPage
方法或showDialog
方法。
goLoginPage
此处逻辑是判断用户sessionid
和当前用户账号
是否为空,如果为空,进入到LoginMainActivity
(登录界面的Activity),如不为空(登录状态),进入到IndexActivity
(应用程序主界面的Activity)。
showDialog
此处逻辑是,弹出提示框“网络安全连接异常时”,用户如果点击重新加载的按钮,则最终进入到sendUpdMessage
方法,如果点击退出按钮,则kill掉当前进程。
继续分析
正常情况下,打开一个app时,应先进入到登录界面,因此这里主要看goLoginPage
方法。即当用户session和当前账号为空,进入到LoginMainActivity
(登录界面的Activity)
通过上面的activity生命周期示意图
可知,先执行onCreate()
方法。
在这个方法里面,实例化了一个LoginMainFrg对象
。鼠标点击,跟进查看。
这里主要关注onActivityCreated()
方法。
注:此处是先执行onCreate()方法,再执行onActivityCreated()方法
在onActivityCreated()
方法中有一个onSuccess
方法,即当这个activity
执行成功后,会去执行该方法,该方法最终调用的是init
方法。
该方法中,关键代码为这两部分
此处hashMap
的值是一个经纬度,该值会被RequestHeadTransport.getHeadPan
处理
跟进到RequestHeadTransport.getHeadPan
就发现熟悉的东西了,也就是上面请求信息搜集到的。
那就在此处利用frida进行简单的hook一下。
frida环境配置
adb
首先需要一个adb连接设备,在夜神模拟器的安装目录的bin目录下即存在该adb文件。这里就直接复制粘贴使用夜神模拟器的adb了。
注:两个adb得版本一致才能连接设备
此时可以看见,能正常获取到设备列表信息
1 | adb devices |
查看该安卓系统的内核版本
1 | adb.exe shell getprop ro.product.cpu.abi |
然后去github下载frida server的x86 版本
frida下载地址:https://github.com/frida/frida/releases
注:由于我本地之前安装过frida,没做升级,所以frida-server下的是低版本的。
然后传入模拟器
1 | adb.exe push frida-server-15.0.18-android-x86 /data/local/tmp |
frida安装
1 | pip3 install frida-tools |
注:此处本地安装的frida和模拟器传入的frida-server版本应该相同。
测试frida
先连接设备,并给予frida-server
执行权限(x),最后运行frida-server
。
注:截图里面没体现su,要记得su一下权限
1 | adb.exe shell |
可以发现,能成功连接设备,并查看到模拟器上的后台进程
frida hook脚本
这里先做流量转发
1 | adb forward tcp:27042 tcp:27042 |
请求数据的hook脚本
前面已经知道,要在RequestHeadTransport
类的getHeadPan
方法处进行hook
。
1 | import frida, sys |
将字符串打印后,可以看见是成功hook到了。其中请求数据的明文,直接在控制台的第一行就被输出了。
简单的处理一下,方便浏览
1 | Java.perform(function () { |
也就将上述所有的数据都拿到了。
继续分析LoginMainFrg
上面的分析,只是拿到了数据,而还没有开始发送数据包。因此继续分析。
生成好的参数,会被传递到init方法
中去。跟进到该方法。
跟进以后,就发现,这个是抓包过程中发现的http接口地址。
分析RxUtil发现响应信息解密
此时进入到RxUtil
中分析
该Util
先初始化TacSdkService
在初始化 Retrofit
。
此时初始化TacSdkService
时,执行init
方法,init
方法调用了Retrofit
的getRetrofit
方法,并最终调用了lambda$getRetrofit$1
。
阅读该方法处的代码,通过变量命名似乎发现了我所需要的东西,即对响应数据解密。
响应数据的hook脚本
1 | var RxUtil = Java.use("gov.xxx.utils.RxUtil"); |
可以看见,脚本成功hook,数据被成功解密。
最终版hook脚本
通过总结可知,以上的分析是通过对WelcomActivity
分析时加载的第一个接口的数据加解密的分析,似乎是只针对这一个接口生效。
通过分析可知,请求数据加密和响应数据解密都是由com.crypto.sm.SMHelper
来完成的。因此这里可以直接hook SMHelper
的sm4Encrypt
和sm4Decrypt
方法。
frida hook脚本如下:
1 | Java.perform(function () { |
可以看见,只要有流量产生,脚本就帮你hook。
既然数据加解密搞定了,其他服务端的漏洞也都能相对的测试一下了。难度不大。
总结
frida hook挺有意思的。之前没有学过,这次恰好遇到了,也就直接现学现用。这个还真是不错。
应该能hook掉这个app的活体检测,等下再研究看看。
注:此处能通过关键字搜索去定位加密函数,一般有加密函数的地方,也就有解密函数。因此不用像我这么麻烦从程序入口开始分析。