某税务app登录接口逆向–获取长效ck
前言:之前分析了web版本的登录接口,各参数都很简单 。有朋友私信我说app登录有意外惊喜,俺们做学术研究的不就是喜欢发掘惊喜么,鉴于此有了此文分析
一、目的
1.1、实现完整登录接口(账号登录、短信登录)
1.2、实现免短信登录接口
1.3、定位算法
二、硬件准备
一部使用magisk(面具)root过的真机,强烈建议直接去某鱼某宝买一个!!!我这边用的是pixel3
三、反编译
3.1、apk本质
我们要知道一个apk文件其实就是一个代码包,把下载下来的apk文件改成zip文件,然后用工具反编译下即可,然后进行初步的分析
3.2、本质
反编译是逆向dex文件,得到的是所用语言的源代码
3.3、工具
jd、jadx都可以,我觉得jadx好用些,后来发现pycharm有个插件也挺好用
反编译后的文件结构:
四、反汇编
4.1、本质
反汇编和反编译是两码事,反汇编可以理解为逆向so文件,得到的是汇编代码
4.2、工具
五、了解apk包结构
so文件为动态链接库(一种二进制文件、c\c++代码文件)一般存放着通用的底层算法库
六、分析加固方式
6.1 第一代 动态加载类
Apk中没有完整原始的Dex,需要运行时动态的加载到内存中
原理
• 落地加载
我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密再将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。运行时首先将我们的Dex文件或者Apk文件解密,然后利用DexClassLoader加载器将其加载进内存中,然后利用反射加载待加固的Apk的Appkication,然后运行待加固程序即可。
• 不落地加载
落地加载将Dex文件解密出来会保存到文件中,再通过DexClassLoader加载进内存中,而不落地加载直接重写DexClassLoader使其可以直接加载字节数组,避免写入文件中。我们要做的是重写DexClassLoader,而这涉及到三个函数defineClass、findClass、loadClass,在一个类被加载的时候,会先后调用这三个函数加载一个类,所以我们需要重写这三个函数。系统的DexClassLoader加载Dex进入内存的也必然是通过字节加载的,而在系统so中的libdvm.so中的openDexFile可以直接加载Dex文件,那么现在清楚了,我们可以通过编写so文件调用openDexFile函数加载Dex字节数组,值得注意的是,openDexFile函数返回值为一个int类型的cookie,可以简单理解成一个dex文件的’身份码’,通过该’身份码’即可操控这个dex文件,至于怎么调用该函数,可以通过dlopen和dlsym函数调用。
• 优点
比较容易实现,无明显的兼容性问题 能有效对抗静态分析和二次打包
• 缺点
启动时需要进行大量的解密运算,容易造成卡死的情况 在内存中的数据为完整的Dex,通过动态调试Dump内存即可获取完整的Dex
Dex字符串加密 资源加密 对抗反编译 对抗调试 Dex动态加载 so加密
6.2 第二代 函数抽取类
原理
主要分为两个步骤指令抽取和指令还原
• 指令抽取
解析原始Dex文件格式,保存所有方法的代码结构体信息,通过传入需要置空指令的方法和类名,检索到其代码结构体信息。通过方法的代码结构体信息获取指令个数和偏移地址,构造空指令集,然后覆盖原始指令,重新计算dex文件的checksum和signature信息,回写到头部信息中。
• 指令还原
native层hook系统函数dexFindClass,获取类结构体信息(dexFindClass函数用于查找类的DexClassDef结构),获取类中所有的方法信息,通过指定方法名进行过滤,获取该方法的代码结构体信息,获取该方法被抽取的指令集,修改方法对应的内存地址为可读属性,直接进行指令还原。
• 优点
加密粒度变小,加密技术从Dex文件级变为方法级 按需解密,解密操作延迟到某类方法被执行前,如果方法不被执行,则不被解密 解密后的代码在内存不连续,克服了内存被Dump的缺点,有效保护了移动客户端的Java代码
• 缺点
使用大量的虚拟机内部结构,会出现兼容性问题,无法保护所有方法 无法对抗自定义虚拟机 它跟虚拟机的JIT优化出现冲突,达不到最佳的性能表现
除一代有的特点外 内存中无完整、连续的Dex so代码混淆、膨胀
6.3 第三代:vpm版本
原理
• VMP
执行到关键代码时进入壳so执行,关于这一点,不同的厂商有着不同的做法,比如把关键函数变成native函数,在壳so中动态或者静态注册
再比如更改关键方法的方法体
无论哪种方法其实都是为了能让壳函数代替原函数去执行 当执行该函数的指令时,解析出指令的OpCode,通过一个巨大的switch case找到处理对应OpCode的函数,然后执行
简单讲就是壳将原本的指令进行一次封装,将原本的指令转换为另一种表现形式
• Dex2C
首先也是将关键代码注册为native函数,主要借助于JNI反射技术,将Java层的方法全部反射为native层,增大分析难度。之后再通过混淆、字符串加密等操作生成so,最后将so进行加固保护。
• 优点
加固强度高,目前没有公开的脱壳工具 经过混淆加密后很难还原原函数
• 缺点
效率较低,启动、运行时都比较耗时稳定性、可控性差,容易产生崩溃
除一、二代全部特点外 so代码虚拟化 对抗之前所有的脱壳方法
七、抓包
7.1、本地抓包
7.1.1、网络证书配置
确保PC和手机端在同一个局域网下,开个热点一起连,谷歌手机在连接wifi后出现不能连接网络的情况
直接使用以下命令:
adb devices//查看是否连接到测试机
adb shell settings put global captive_portal_use_https 1
adb shell settings put global captive_portal_http_url http://captive.v2ex.co/generate_204
adb shell settings put global captive_portal_https_url https://captive.v2ex.co/generate_204
adb shell settings put global captive_portal_mode 0
手动设置代理地址为PC的ip和抓包软件设置的端口号(默认8888),手机访问地址安装证书,默认证书是安装在用户列表里
抓包结果:
7.1.2、root检测
现象就是检测到该应用在root环境下运行,闪退
解决方案:magisk中模块shamiko,有时候高版本不行,那就降版本
7.1.3、网络异常
怀疑是app对抓包的反检测,当前的android12对用户安装的证书不信任导致的,建议直接使用magisk的三方模块将证书转移至系统目录,安装完模块直接重启设备,证书就自动移到系统目录了
7.1.4、证书错误
中途更换了抓包工具亦如此,系统证书检测、app证书检测,怀疑后者,但是后来换热点、换设备就可以了(第一点换热点也可以解 决),无解
这个问题目前没有根治
7.2、远程抓包
部署个远程服务器,在上面装个抓包软件,手机连接wifi手动设置代理地址为服务器端的ip和抓包软件设置的端口号
八、脱壳准备
8.1、了解frida框架
8.2、两端(PC、DEVICE)环境搭建
8.2.1、PC
8.2.2、设备端
安装与之版本匹配的frida-server,后面的代码注入都依赖他,先在PC端下载好,再用adb推送过去
8.2.3、常见问题及解决策略
1、推送的是文件,而不是目录,否则在启动的时候会出现以下情况
2、赋予文件最高权限
3、修改文件名避免基本的frida检测
4、frida-server版本不对,会出现以下情况
8.3、启动、测试连接
8.3.1、查看当前设备运行进程 frida-ps -U
8.3.2、先启动设备端frida-server
8.3.3、pc端建立连接
8.3.4、常见问题及解决策略
1、进程异常退出
连接过程进程会异常退出,强行启动app依然如此,发现app可能对frida的特性进行了检测
使用魔改版本的frida——hluda
再次下载、推送、启动并指定端口
2、再次连接依然报错
端口转发过后,就ok了
至此,PC端与设备端正式建立了frida服务框架,确保frida的正确运行
九、so脱壳
反编译之后,在lib库里面发现一堆so文件,当即对其进行了反汇编,发现大部分文件都损坏了,打不开
请教业内前辈,才知道so文件是加固过的,可以尝试从内存中dump出来,原理就是,设备端运行app,会自动对加壳的文件进行脱壳,再加载到内存中,基于此,可以直接从内存中读取
9.1、编写准备注入的js
function dump_so(so_name) {
Java.perform(function () {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
console.log("活动进程:", currentApplication)
var dir = currentApplication.getApplicationContext().getFilesDir().getPath();
console.log("目录:", dir)
var libso = Process.getModuleByName(so_name);
console.log("目标so文件路径:", libso)
console.log("[name]:", libso.name);
console.log("[base]:", libso.base);
console.log("[size]:", ptr(libso.size));
console.log("[path]:", libso.path);
var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'rwx');
var libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}
console.log("js注入成功, 准备dump!!!")
9.2、连接、启动、注入
9.3、锁定so文件位置
9.4、导出
9.5、查找算法
至此,so文件全部脱壳完成,也仍然存在小部分文件损坏了,这个也可以用脚本进行修复
十、dex脱壳
10.1、下载frida_dexdump
10.2、启动frida-server
10.3、导出
导出重新扔到jadx里编译,发现也没找到与包名类似的文件夹,怀疑没脱全
10.4、二次导出
切换app到登录页面,使用深度搜索
看着比之前文件多了一倍,重新扔到jadx里面,发现有报错,仍然没看到类似的文件夹,尝试用pycharm里的插件jadx打开
看着比之前文件多了一倍,重新扔到jadx里面,发现有报错,仍然没看到类似的文件夹,尝试用pycharm里的插件jadx打开
至此,dex文件全部脱壳完成,接下来就是找寻各个接口中参数加密的地方
标签:ck,文件,--,app,Apk,so,Dex,libso,加载 From: https://blog.csdn.net/m0_75266682/article/details/141867426