目前来看,APK的内容比较少,考虑到扩展性,还是分开说。
文章目录
基本功
开发方面有一个常识贴,https://blog.csdn.net/weixin_42100211/article/details/113762797
this.getResources().getString(0x7F030001)
这种都是取常量。
先在R中按括号内的编号找name,
然后在apk的resource目录下找string.xml,在其中看刚才的name对应的字符串内容。
context
Context承受的两大重要职责是:身份权限、程序访问系统的接口。
一个Java类,如果没有context那么就是一个普通的Java类,而当他获得context那么他就可以称之为一个组件了。
toast
Toast是Android系统提供的一种提醒方式,将一些短小的信息通知给用户,这些信息会在一段时间后自动消失。
APK目录结构
- resources.arsc 和 res目录,分别是编译和未编译的资源文件。assets目录中是assetManager能访问的资源文件。
- lib目录,平台相关的库文件。
- AndroidManifest.xml,JEB直接打开apk就可以读到。
- classes.dex,Android运行时可执行文件。包含所有Java层代码。
- meta-inf目录,cert.sf(摘要), cert.rsa(签名),manifest.mf(清单文件)。
ab文件与sqlite
.ab 后缀名的文件是 Android 系统的备份文件格式。它分为加密和未加密两种类型,如果加密,在前24个字节中会有AES-256的标志,如果未加密,则在前24个字节中会有none的标志。
可以通过abe.jar(android-backup-extractor)将其转换成.tar文件然后进行解压:
java -jar abe.jar unpack xxx.ab xxx.rar
我所遇到的题目(攻防世界 app3)解压后得到apk文件和db文件。sqlite则常用于android。getWritableDatabase()可接受密钥作为参数。用DB Browser for SQLCipher加上密钥可以打开。
现实中的手机可能会在原生备份文件的基础上再加一个自己的文件头。
Jeb静态分析注意事项
拿到先看manifest,看入口点。
如果JEB出现// Method was not decompiled的提示,smali还是可以看的(而不代表脱出来的dex有问题)。我理解为JEB的谨慎,没把握就不显示了。此时可以用dex2jar + jdgui再试试(青龙组2020 bang,jeb不行而dex2jar+jdgui就行了)。
d2j-dex2jar <dexFilePath> -o <outputJarFilePath>
apk 动态调试
我手里的破解版jeb的能力范围是dex,arm,arm64。下面脚本中的am是activity manager的缩写。
- 在已开启usb调试的模拟器中安装apk,然后用已配置环境变量的adb连接。夜神模拟器的默认端口是62001。包名(package name)和主窗体可替换。
adb connect 127.0.0.1:62001
adb shell am start -D -n com.example.findit/.MainActivity
- ctrl b下断点。
- 常用操作是,在invoke string valueof之后,将int型修改为string型。
解题经验
assets目录中可能会有flag图片。
arm so动态调试
so逆向不仅仅是为了出题,实际运用中也喜欢将需要保护的代码放在so。
我的一个感悟是,如果apk非常的大,乱七八糟,找不到关键函数,建议直接找native调用(在bytecode中正则表达式搜整词,\bnative\b)、直接分析so。
有时会看见loadlibrary(xxx),需要解压apk到lib里找xxx.so。然后用ida分析so即可。
如果没有loadlibrary给出so名称,只能希望总共只有一个so。
加载过程:.init -> .init array -> JNI_Onload -> java_com_xxx
函数导出表里可以直接导出java层的native方法,则为静态注册;如果导出表里没见到,只有JNI_Onload,则为动态注册。这两种方法的选择似乎无关乎安全,在开发上都是可以采用的。如果是静态注册,在IDA左侧一般就能看见显眼的JAVA_COM_XXX。如果是动态注册,.data.rel.ro.local,这个段存放的是动态注册的函数。
静态调试,有两个极为常用的技巧:
改为JNIEnv*类型;导入jni.h。
在JNI的C代码调用Java代码。实现原理:使用JNI提供的反射借口来反射得到Java方法,进行调用。特征语句是JNIEnv * env findclass和 env GetMethodID和 callxxMethod(xx为基本数据类型,如int或void)。
arm的so,不能用python直接动态调了,会提示无效的win32。可以用ida来动态调试。但还是需要用jeb打开,看看AM,主要看包名、activity名和debuggable,不能的话需要做个重打包。
可以用ctrl z来停止监听。
有的apk里,只有这两种so。
arm64-v8a:ARMv8,64位
armeabi-v7a:ARMv7,32位
貌似我夜神模拟器目前使用的机型(下图中的LIO-AN00)是32位的,因为:
LIO-AN00:/data/local/tmp # ./android_server64
/system/bin/sh: ./android_server64: not executable: 64-bit ELF file
https://zhuanlan.zhihu.com/p/58468014 这篇文章从零开始教。环境配置时有几个注意的点,
在https://www.androiddevtools.cn/的sdk tools下载名字类似android-sdk_r24.4.1-windows.zip的包,而不用乱下一些别的东西(比如AS),里面有monitor.bat。monitor.bat的运行可以用java 8,用java 11则不行。
步骤总结:
- 启动模拟器,安装debuggable的apk
- adb启动模拟器里的android server(事先用adb push将ida目录下的dbgserv里的对应文件传到/local/data/tmp,也可以其它目录)
- 设置端口转发,adb forward tcp:23946 tcp:23946(adb forward --list检查)
- adb shell am start -D -n package/ActivityName,此时可以看见waiting for debugger
- 选ida的remote arm debugger,attach process。记得debuger option打三个勾。
- ddms查看端口,jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=斜杠前面数字
反调试,ctrl+s到init array段看看有没有tracepid(目前我只见过while 1轮询tracepid这一种反调试),export表里找JNI onload。
五代加固
这张图成图时间不清楚。可以确定的是:
360加固、梆梆加固、腾讯乐固、百度加固免费版可自动脱,项目地址https://github.com/OakChen/ApkShelling。所以说,PKID查壳的流程还是得走,万一能自动脱呢?
动态加载和自动脱壳机
除了so,保护代码的另一个方法是动态加载。
反射,动态获取内容、动态调用方法。
安装xposed框架和反射大师,可以导出实际运行的dex。然后用adb pull就能拿回来放进ida了。
目前的自动脱壳机,具备memory dump功能和反射脱壳功能。我个人用反射大师脱过梆梆免费版。
按网友说法,反射脱壳有四个问题:
- odex导致类加载行为相应发生了变化,android8.0后难以获取dex对象
- 反射的操作单位是类,类外的东西是拿不到的
- 第三代指令抽取会将一部分类放在native层,所以部分类脱不到
- 重写attachBaseContext然后禁用hook,就能彻底阻断反射脱壳
安卓手游
直接解压apk。Unity3D 安卓游戏的主逻辑都在 asserts/bin/data/Managed/Assembly-CSarp.dll。
重打包
./apktool d C:\Users\ysnb\Downloads\attachment\PixelShooter.apk
修改文件
./apktool b PixelShooter
注意,默认输出位于目录/dist下。
如果之前没生成过密钥的话,就生成一个。
chcp 936
keytool -genkey -alias abc.keystore -keyalg RSA -validity 20000 -keystore abc.keystore
问是否正确时输入y。
chcp 936
jarsigner -verbose -keystore abc.keystore -signedjar shooter_signed.apk ./PixelShooter/dist/PixelShooter.apk abc.keystore
这一次签名的输出在当前目录下。
反调试
- 将原始的签名硬编码,启动时检查签名是否与内置签名相同
脑测可以通过修改内置硬编码来绕过,未经实践。 - 对23946端口进行判断
可以通过 ./android_server -p 23947 ,将IDA调试端口改为23947或者其他端口,注意端口转发和IDA调试的端口号都要改成23947。 - 轮询tracerpid,大于0说明被调试
标志是getpid函数和新建线程while 1。 可以修改系统源码后重新编译,让 Tracepid 永远为0。解包boot.img,解压内核,修改,压缩内核,打包boot.img。 - 进程名检测
如果进程名中包含android_server,gdbserver,gdb等名称,则认为程序当前被动态调试,退出程序。可以将android_server改成其他名字然后运行。
通用的绕过方法是,看exit的交叉调用,然后patch,然后重打包。
模拟器检测
我注意到模拟器一般是无SIM卡的,可以辅助判断。
附录
从taptap获取游戏研究素材
1.酷安,下载安装mt管理器
2.侧滑栏→安装包提取→找到并点击taptap
3.进数据目录1, 点击virtual文件夹
4.依次进入data/app/
5.进文件夹找到要安装的apk
arm汇编的样貌
其中,BL为跳转,这里的作用类似call。