背景
公司某同事,想通过python脚本自动调用某应用接口,以达到自己的目的(你懂得)。但是通过抓包发现,几乎所有接口里面都两个公共字段"timestamp"和"sign"。timestamp是时间戳很好得到,但是"sign"却不停在变化,猜测后端会根据"sign"字段值,对数据做校验。于是开启了对该App逆向路程。
内容
- 逆向工具
- 原生层逆向
- so逆向
- so动态调试
逆向工具
-
apktools
- apktool d [apk]
将apk反编译smali文件形式,通过这种方式,得到的资源文件不会出现乱码的,并且可以重新编译成apk,签名安装。 - apktool b [smali目录]
重新生成apk文件,记得要重新签名,不然安装不了。
- apktool d [apk]
-
dex2jar
将apk解压得到的dex文件反编译成jar文件,方便查看- ./dex2jar/d2j-dex2jar.sh [dex文件]
生成jar文件
- ./dex2jar/d2j-dex2jar.sh [dex文件]
jd-gui
查看jar文件,配合搜索可以方便快速定位代码位置jadx
除了可以像jd-gui查看jar文件,还可以对混淆字段重新取名字,更加方便查看,更多功能请自行查看。ida
查看so文件,so动态调试等功能
原生层逆向
dex生成jar文件
更具前面介绍,接口请求参数里面都会添加"sign"字段,那么我们可以全局搜索"sign",查看跳转路径,找到"sign"字段值生成的地方,最终发现native层把接口传递参数转成json字符串后,调用JNI层方法返回一个字符串,这个字符串就是"sign"的值。这下无可奈何了。-
apk生成smali文件
为了确定"sign"值就是调用JNI方法生成的,于是我找到了登录页面,找到登录按钮,通过这里查看调用逻辑。-
如何快速定位到“登录按钮”点击事件处理的地方
由于点击事件监听处,都是10进字的常量值,所有并不知道那个是“登录按钮”,而且该apk的资源文件混淆,无法直接找到登录页面的布局文件。这时,可以借助Android Studio查看apk功能,找到resources.arsc,把10进字转换成16进字,然后在resources.arsc中找到对应的文件,属性名。
截屏2020-12-17 下午3.42.54.png
-
so逆向
用ida打开so文件,入下图:
该apk调用的是getApiSign这个jni方法,返回的"sign"值。
-
arm转c
F5快捷键可以将arm指令转化成可读的C语言
截屏2020-12-17 下午4.35.08.png
转成C语音后,就是这样,如果能够看懂,当然可以直接找出计算"sign"值算法,如果看不懂,那么可以使用so动态调试,查看相应的变量值。
注意:这里要用32的ida,64位不支持转换且动态调试会报错。
so动态调试
so动态调试,需要root手机一台。
-
复制android_server(IDA目录>dbgsrv>android_server)到设备中
1. adb push android_server /sdcard 2. adb shell 3. mount -o rw,remount /system (出于安全考虑,操作完之后,把文件改回只读属性:mount -o ro,remount /system) 4. mv /sdcard/android_server /system/bin 5. chmod 777 /system/bin/android_server
-
启动android_server
- su do 切换到root权限(如果不是root权限,后面ida调试找不到应用进程)
- ./android_server (root权限下启动服务 )
- 新启命令行执行:adb forward tcp:23946 tcp:23946建立端口转发。
- adb forward --list
查看端口映射列表 - adb forward --remove tcp:23946
移除建立的端口转发
-
在手机上运行该apk
由于其它原因,我把计算"sign"字段的so库导入我自己新建的android工程,然后写了一个简单的点击事件去调用该jni方法。
截屏2020-12-17 下午5.01.21.png -
ida动态调试so
打开32位ida,点击"go"打开一个空白页,选择要调试的进程。
截屏2020-12-17 下午5.03.36.png
-
使用Ctrl+S找到需要调试so的基地址:EF606000
截屏2020-12-17 下午5.07.25.png -
然后通过另外一个IDA打开so文件(可以用64位的打开so),查看函数的相对地址:1794
截屏2020-12-17 下午5.10.50.png
那么得到了函数的绝对地址就是:EF607794,使用G键快速跳转到这个绝对地址:
点击左边蓝点,下断点,点击运行,然后在应用点击,调用getApiSign JNI方法。
触发getApiSign JNI方法调用后名,程序执行到图中红框地方,按F8进行单步调试,F9运行,通过R0寄存器找到算法使用的密钥。
拿得密钥后,就可以用其它语言实现。整个破解过程就到这里。
-
其它配置
这里是so在调试之前就已经加载,如果要停留在so加载的地方,那么可以打开Debugger setup,配置如下:
截屏2020-12-17 下午5.26.32.png 使用am命令
adb am start -D -n com.example.testarm/com.example.testarm.MainActivity
启动应用,让进程处于等待调试中,然后用ida attach进程,进行调试,适用在apk启动时就加载so并且需要断点so加载过程。-
这里为什么会断在libc.so中呢
android系统中libc是c层中最基本的函数库,libc中封装了io、文件、socket等基本系统调用。所有上层的调用都需要经过libc封装层。所以libc.so是最基本的,所以会断在这里,而且我们还需要知道一些常用的系统so,比如linker我们知道,这个linker是用于加载so文件的模块,所以后面我们在分析如何在.init_array处下断点
还有一个就是libdvm.so文件,他包含了DVM中所有的底层加载dex的一些方法,我们在后面动态调试需要dump出加密之后的dex文件,就需要调试这个so文件了。