银行App逆向实战:从脱壳到登录接口的完整安全分析
1. 项目概述与核心目标最近在安全研究圈子里讨论银行类App安全机制的话题热度一直不减。很多朋友无论是出于安全研究、漏洞挖掘还是单纯的技术好奇都想了解一个头部银行的App究竟是如何构建其防御体系的以及我们作为研究者如何一步步拆解它。今天我就以一次真实的、针对某头部银行App的逆向分析过程为例和大家完整分享从拿到APK到完成脱壳再到最终定位到核心登录接口的全过程。这不仅仅是一次技术操作的记录更是一次对现代App加固与防护逻辑的深度思考。整个过程涉及静态分析、动态调试、脱壳修复等多个环节我会把每个步骤的原理、踩过的坑以及最终验证的方法都讲清楚目标是让你看完后不仅能复现更能理解背后的“为什么”。这次分析的目标App版本信息我就不具体透露了但可以告诉大家它采用了业界主流的、强度较高的商业化加固方案。我们的核心目标有三个第一成功绕过或剥离其加固外壳获取到可读的DEX字节码第二在脱壳后的代码中精准定位到用户登录这个关键业务功能的实现逻辑第三理清登录请求的完整数据构造流程包括参数加密、签名算法等。这整个过程就像是在解一个设计精巧的谜题需要耐心、工具和对Android系统机制的深刻理解。2. 分析环境与工具链准备工欲善其事必先利其器。在开始逆向之前搭建一个稳定、高效的分析环境至关重要。这个环境需要兼顾静态分析和动态调试的需求。2.1 核心设备与系统配置我选择了一台Root后的Android真机作为动态分析的主力环境。模拟器虽然方便但很多加固方案会检测运行环境在模拟器上可能会触发反调试或直接崩溃导致分析无法进行。真机的系统我刷成了相对纯净的Android 9Pie版本这个版本对大多数调试工具兼容性好且系统API足够新以运行目标App。确保设备已通过USB调试连接电脑并授权了调试权限。在电脑端我使用的是Windows 10系统但工具链以跨平台的Python和Java生态为主。准备一台性能尚可的电脑因为脱壳和反编译过程可能比较消耗内存和CPU资源。2.2 逆向分析工具清单工具的选择没有绝对的标准但一套顺手的工具能极大提升效率。下面是我这次使用的主要工具及其作用静态分析工具Jadx-GUI这是首选的DEX反编译器它能够将DEX、APK文件直接反编译成可读性非常高的Java代码。它的图形化界面方便搜索、跳转和查看继承关系是静态分析的起点和主要阵地。Apktool用于解包APK文件获取其中的资源文件res、清单文件AndroidManifest.xml和未解压的DEX文件。在脱壳后我们也需要用Apktool重新打包APK进行测试。Bytecode Viewer作为Jadx的补充有时Jadx反编译某些混淆严重的代码时可能出错或可读性差Bytecode Viewer可以查看更底层的Smali汇编代码便于精准分析。IDA Pro主要用于分析APK包内的原生库.so文件。银行的加密逻辑很可能放在Native层用C/C实现以增加逆向难度。IDA是进行静态反汇编和动态调试.so文件的不二之选。动态调试与脱壳工具Frida这是现代移动安全分析的“瑞士军刀”。它是一个动态插桩框架通过注入JavaScript代码到目标进程可以实时Hook函数、修改参数、打印调用栈等。我们将用它来追踪加固壳的解壳流程和关键函数的调用。Objection一个基于Frida的命令行工具封装了许多常用功能比如绕过SSL Pinning证书绑定、内存搜索、执行命令等可以快速完成一些基础性工作。Xposed框架EdXposed/LSPosed在Root环境下安装的框架允许加载模块来Hook系统级或应用级的Java方法。对于一些Frida可能被检测的场景Xposed模块是另一种有效的动态分析手段。我准备了一个自写的脱壳模块用于在目标App运行时从内存中Dump出解密后的DEX字节码。adbAndroid Debug Bridge必备的调试桥梁用于安装应用、推送文件、抓取日志等。网络抓包与辅助工具Burp Suite / Charles用于拦截和查看App的网络请求。这是定位登录接口最直观的方式。需要将代理证书安装到手机并配置好代理以捕获HTTPS流量。Postman在分析清楚登录接口的参数后用于模拟请求、测试接口验证我们的分析是否正确。Python3环境准备一些脚本用于辅助分析比如解密抓包数据、批量处理文件、计算哈希等。注意所有工具请务必从官方或可信源获取。分析过程中产生的所有数据仅用于本地学习研究切勿进行任何未授权的测试或攻击。2.3 环境搭建的避坑要点搭建环境时最容易出问题的地方是Frida的环境配置。首先确保电脑端frida-tools和手机端frida-server的版本完全一致否则会出现连接失败。frida-server需要根据手机CPU架构通常是arm64下载对应的版本并通过adb push到手机赋予可执行权限后运行。可以用frida-ps -U命令来测试是否连接成功。另一个常见问题是网络抓包工具无法捕获HTTPS请求。这通常是因为App采用了SSL Pinning证书绑定技术。我们需要先用Objection或自写的Frida脚本绕过这一机制。使用Objection时在连接上目标App后运行android sslpinning disable命令通常可以解决大部分问题。如果不行就需要手动分析App的证书校验逻辑并用Frida进行Hook。3. 初探与加固识别拿到目标APK文件后不要急于直接扔进反编译工具。第一步应该是进行“体检”了解我们面对的是一个什么样的对手。3.1 基础信息收集使用aapt工具或者直接通过Apktool解包后查看AndroidManifest.xml可以获取App的包名、版本号、权限声明、入口Activity等信息。这些信息对于后续的Hook和启动调试至关重要。特别是入口Activity它是我们启动App和附加调试器的关键。接下来我习惯先用file命令或者直接解压APK查看其内部结构。一个明显的特征是经过强加固的APK其原始classes.dex文件通常很小可能只有几十KB甚至被替换成了一个无效的或引导性的DEX。而真正的应用代码被加密后隐藏在了assets文件夹、lib文件夹下的.so文件内或者被分割成多个小文件。在这个银行App中我就发现classes.dex体积异常小而在assets目录下存在几个名称可疑的、体积较大的.dat或.bin文件同时在lib/arm64-v8a下有几个名称非标准的.so库这都强烈暗示了加固的存在。3.2 加固方案特征判断通过上述迹象结合行业经验可以初步判断其使用的加固方案。目前市面上主流的第三方加固厂商如360加固保、腾讯乐固、梆梆安全、爱加密等都有其特定的特征。例如某些加固会在AndroidManifest.xml的application节点下插入特定的meta-data或者其解壳的Application类有固定的命名规律如StubApplication。一个快速验证的方法是使用一些开源的加固识别工具比如PackersDetector或者直接搜索已知的加固特征字符串。更直接的方法是使用Frida在App启动时枚举当前加载的所有类寻找可疑的类名。我通常写一个简单的Frida脚本在Java.perform后使用Java.enumerateLoadedClasses来查看经常会发现一些包含“shell”、“protect”、“stub”、“wrapper”等关键词的类这些就是加固壳的“马甲”。识别出具体的加固方案非常重要因为不同方案的脱壳思路和工具可能不同。网上有大量针对特定加固版本的脱壳脚本或Xposed模块。这次分析的目标经过特征比对确认使用了某主流厂商的较新版本加固。4. 动态脱壳从内存中提取DEX静态分析加固后的APK几乎得不到有效代码因此动态脱壳是必须跨越的一步。其核心原理是无论外壳多么复杂最终都必须将解密后的、真正的DEX文件加载到内存中并由Android的Dalvik/ART虚拟机执行。我们的目标就是在正确的时机从内存里把这个完整的DEX镜像“捞”出来。4.1 脱壳时机与Hook点选择脱壳的关键在于时机。太早DEX还没被解密加载太晚部分类可能已经被卸载或混淆。一个经典的Hook点是dalvik.system.DexClassLoader或PathClassLoader的loadClass方法或者更底层的DexFile相关方法。但对于高版本的Android和复杂加固这些点可能被绕过。更通用的方法是HookClassLoader的defineClass方法或者ART虚拟机下的DexFile::OpenMemory等Native函数。这需要一定的Android系统源码知识。幸运的是社区已经有了很多成熟的方案。我选择使用一个知名的、针对该加固版本的Xposed内存脱壳模块。它的原理是通过Xposed Hook系统底层加载DEX的关键函数当检测到目标App的进程在加载DEX时便将这块内存区域完整地Dump保存到文件。4.2 脱壳实操过程安装环境在已Root的手机上安装好Xposed框架我用的LSPosed并激活我们准备好的脱壳模块。在LSPosed中配置该模块只作用于目标银行App。启动与Dump启动目标银行App。脱壳模块会在后台静默工作。当App完成启动并进入登录界面后我们通过文件管理器查看模块指定的输出目录通常是/sdcard下的某个文件夹。理想情况下这里应该出现了几个.dex文件大小从几百KB到几MB不等这很可能就是被成功Dump出来的、解密后的DEX文件。文件验证将这些Dex文件拷贝到电脑上先用file命令查看类型确认是有效的DEX文件然后尝试用Jadx打开其中一个。如果能看到大量有意义的Java类名和代码逻辑而不是全是混淆的a/b/c类名说明脱壳基本成功。实操心得一次启动可能不会Dump出所有DEX有些DEX是按需加载的。可以多操作几个App内的功能比如点击登录、进入主页等触发更多代码加载然后重新Dump一次确保完整性。另外Dump出的DEX可能有多份对应着主Dex和从Assets加载的插件化Dex需要全部收集。4.3 脱壳后修复与合并直接Dump出来的DEX文件可能无法被反编译工具完美识别有时会报错。常见问题包括Dex头信息损坏、校验和不匹配等。这时就需要进行修复。一个常用的工具是dexfixer或者一些集成修复功能的脱壳工具。修复的原理通常是解析Dump出的内存数据根据DEX文件格式规范重新组装出正确的文件头、映射表等。对于本次分析我使用的脱壳模块Dump出的DEX质量较高Jadx可以直接打开。但如果遇到问题可以搜索“DEX修复”相关工具和教程手动进行修复。如果得到了多个DEX文件为了分析方便可以使用d2j-dex2jar工具将它们先转换成Jar包或者直接用Jadx的“Open File”功能加载多个DEX它会自动尝试建立索引。但更推荐的方式是使用dexmerger等工具将它们合并成一个大的classes.dex这样在代码中跳转会更加方便。5. 静态分析定位登录逻辑成功获取到清晰的DEX代码后我们就进入了主战场——静态分析。目标是从数十万个类和方法中找到负责登录的那几行关键代码。5.1 关键词搜索与入口定位登录功能的前端入口通常是Activity。在Jadx中我们可以进行全局搜索。搜索界面相关搜索包含“login”、“signin”、“account”、“密码”、“password”等词汇的类名、方法名或字符串资源。重点关注继承自Activity、Fragment或AppCompatActivity的类。找到可能是登录界面的Activity例如LoginActivity、AccountLoginFragment等。搜索网络请求相关登录必然伴随网络请求。搜索项目中使用的网络库的关键类如OkHttpClient、Retrofit、HttpURLConnection的调用处。或者搜索常见的API域名、URL路径片段。有时在strings.xml或常量类里能找到配置的Base URL。搜索加密相关银行App的登录参数大概率是加密的。搜索“encrypt”、“decrypt”、“AES”、“RSA”、“MD5”、“SHA”、“sign”等关键词找到加密工具类。这些类往往是登录参数构造流程中的关键一环。通过交叉搜索我很快定位到了一个名为com.xxx.bank.module.login.view.LoginActivity的类。查看其onCreate方法找到了用户名、密码输入框以及登录按钮的初始化代码。顺藤摸瓜找到了登录按钮的点击监听器OnClickListener。5.2 代码追溯与流程分析在点击监听器中通常会调用一个Presenter或ViewModel层的方法比如login(username, password)。我们需要跟进这个方法。业务逻辑层进入Presenter或ViewModel这里会进行输入校验、组装请求模型等操作。关键是要找到它最终调用的“数据层”或“网络层”方法该方法会发起实际的网络请求。网络请求层跟踪到一个可能是Repository或ApiService的类。这里定义了具体的API接口。如果使用Retrofit接口会以注解形式声明例如POST(/v1/user/login) CallLoginResponse login(Body LoginRequest request);这行代码直接告诉了我们登录接口的路径和请求体是一个LoginRequest对象。请求模型分析找到LoginRequest类分析其字段。通常会包含username、password但这里的password肯定不是明文。查看其构造方法或设置方法会发现它调用了某个EncryptUtil.encryptPassword()之类的方法。这时之前找到的加密工具类就派上用场了。加密流程还原深入分析EncryptUtil。登录加密可能不是简单的一次加密。常见的流程是先对密码进行MD5或SHA256哈希然后可能拼接时间戳、随机数nonce再用RSA公钥加密或者采用AES加密而AES的密钥本身又通过RSA加密传输。需要仔细阅读代码理清每一步的顺序和使用的算法、密钥。用笔记或画图的方式记录下来。在这个银行App中我梳理出的流程是密码先进行SHA256哈希然后拼接一个从服务器获取的会话盐salt和一个时间戳再次哈希最后使用一个硬编码在so库中的AES密钥进行CBC模式加密。用户名则是明文传输。整个加密逻辑的核心部分特别是AES密钥的最终处理被放在了Native层.so文件中这增加了逆向难度。6. 深入Native层与算法还原当关键逻辑下沉到Native层我们就需要使用IDA Pro进行静态分析并结合Frida进行动态验证。6.1 定位关键Native函数回到Java层找到调用Native方法的地方。代码中会有类似public static native String encryptData(String data);的声明并且有System.loadLibrary(crypto-core)加载对应的so库。记下这个Native方法名和so库名这里是libcrypto-core.so。用Apktool解包原始APK在lib/arm64-v8a根据手机架构目录下找到libcrypto-core.so文件用IDA Pro打开它。6.2 IDA静态分析与Frida动态Hook在IDA中由于so文件通常被剥离了符号表Stripped我们看不到encryptData这样的函数名。这时有几种策略字符串搜索在IDA的Strings窗口中搜索在Java层看到的、可能传递给Native层的常量字符串比如“AES/CBC/PKCS5Padding”等。找到引用这些字符串的代码位置就可能接近目标函数。导出函数查看查看Exports标签页虽然函数名被混淆但Java_开头的JNI函数名会被保留因为需要被Java虚拟机动态链接。寻找类似Java_com_xxx_bank_util_Crypto_encryptData的函数这就是我们的目标。动态定位这是更高效的方法。使用Frida Hook这个Native函数。先写一个Frida脚本在Java.perform后使用Interceptor.attach来附加到这个Native函数的地址上。我们可以通过Module.findExportByName(“libcrypto-core.so”, “encryptData”)来尝试查找如果符号被剥离这个方法会失败。更可靠的方法是HookSystem.loadLibrary在so库被加载后枚举其所有导出函数根据特征如参数为两个jstring返回jstring来猜测和尝试Hook。我采用了动态结合静态的方法。先用一个Frida脚本在App启动后枚举libcrypto-core.so的所有导出函数并打印出来。虽然名字是混淆的但通过观察函数参数的个数和类型通过Frida的args查看可以筛选出可能的候选。然后我写一个脚本批量Hook这些候选函数打印它们的输入和输出。当我在App界面上点击登录时观察哪个被Hook的函数收到了密码明文或中间哈希值并输出了密文就锁定了目标函数。6.3 算法还原与模拟锁定函数后在IDA中通过函数地址跳转到对应的汇编代码。虽然阅读ARM汇编有门槛但结合动态调试我们可以理解其大致逻辑。Frida Hook可以打印出函数的所有输入参数、返回值甚至可以通过修改内存来测试。更重要的是我们可以不深究汇编细节而采用“黑盒”模拟的方式。既然知道了输入和输出的对应关系以及这个函数在Java层被调用的上下文我们可以用Frida的NativeFunction功能或者更简单地直接用Python的ctypes库来加载这个so文件并调用这个函数。前提是需要知道函数的准确签名参数类型和返回类型。我最终没有完全逆向其汇编而是通过Frida Hook记录了多组“明文输入-密文输出”的数据对。然后我编写了一个Python脚本使用frida的Node.js绑定frida-compile来在电脑端模拟运行这个Native函数成功复现了加密过程。这足以让我在Postman中构造出合法的登录请求。7. 接口验证与流程串联至此我们已经掌握了登录接口的URL、请求方法POST、请求体结构以及密码的加密算法。最后一步是串联所有信息并进行验证。7.1 使用Postman模拟请求新建请求在Postman中新建一个POST请求URL填写完整的登录接口地址例如https://api.xxxbank.com/v1/user/login。构造请求头分析抓包数据或代码添加必要的Headers如Content-Type: application/json、User-Agent、App-Version等。有些银行App还会有自定义的头部比如设备ID、会话ID等这些可能需要从App启动时的其他接口获取。构造请求体按照LoginRequest类的结构构造JSON。例如{ “username”: “13800138000”, “password”: “这里是经过完整加密流程生成的密文字符串”, “timestamp”: “1646389200000”, “nonce”: “abcdefg” }其中password字段的值使用我们还原的加密算法Python脚本对原始密码进行计算得到。timestamp和nonce需要按照代码逻辑生成通常是当前毫秒级时间戳和一个随机字符串。发送与调试发送请求。如果请求成功我们会收到服务器返回的响应其中可能包含登录成功的token、用户信息等。如果失败返回错误码如“密码错误”、“参数无效”则需要检查加密算法是否完全正确、请求头是否遗漏、参数名是否与代码中定义的字段名完全一致大小写敏感、时间戳是否在服务器允许的误差范围内等。7.2 完整流程复盘与难点总结回顾整个分析过程难点主要集中在三个地方脱壳的稳定性高版本加固对抗性强脱壳模块可能需要针对特定版本调整。有时需要尝试多种脱壳工具或方法如Frida内存扫描Dex、调试器Dump等。代码混淆与逻辑分散即使脱壳成功代码也可能被严重混淆类名、方法名、变量名无意义并且业务逻辑可能被分散到多个模块或插件中。这需要极强的耐心和代码跟踪能力善于利用Jadx的“查找用法”、“跳转到声明”功能以及通过字符串、资源ID等线索进行关联。Native层保护核心算法放在so库中并可能进行代码混淆、反调试、完整性校验等。这要求分析者具备一定的ARM汇编阅读能力和动态调试经验。Frida在此时是无价之宝可以绕过很多反调试并快速验证猜想。这次对某头部银行App的逆向分析是一次典型且完整的实战。它涵盖了从加固识别、动态脱壳、静态分析到Native层Hook的完整链条。技术本身是中性的掌握这些技能能让我们更深刻地理解移动应用安全的设计与实现从而在开发更安全的App或进行更专业的安