APP签名验证全栈破解:Frida Hook绕过+算法逆向+Python一键调用(附可运行脚本)
前言
做过APP逆向和爬虫的同学,肯定都被签名验证折磨过:改了一行代码重新打包,APP直接闪退;抓包看到接口有个sign参数,翻遍JS找不到加密逻辑;好不容易找到加密函数,一调用就崩溃。
这就是现在APP最常用的双重签名防护:客户端APK签名校验+服务端接口签名验证。前者防止你篡改APP,后者防止你伪造请求。很多人卡在第一步,改完APK就闪退,根本没机会碰接口。
本文基于真实项目实战,分享一套通用的签名验证破解方案。从客户端APK签名校验的Frida Hook绕过,到服务端sign算法的逆向还原,最后用Python实现一键调用。全程无废话,所有代码都经过实测,看完你就能破解市面上90%的普通APP签名验证。
一、APP签名验证体系全景
先搞清楚我们要面对的是什么。现在的APP签名验证已经形成了完整的闭环防御,任何一个环节被突破,都会导致APP无法正常运行。
1.1 两层签名验证详解
| 验证层级 | 验证目的 | 常见实现方式 | 破解难度 |
|---|---|---|---|
| 客户端APK签名校验 | 防止APK被篡改、二次打包 | 校验APK签名哈希、证书指纹、包名 | ★★★☆☆ |
| 服务端接口签名验证 | 防止接口被恶意调用、伪造请求 | MD5/SHA1/HMAC/AES加密请求参数 | ★★★★☆ |
核心结论:客户端校验是第一道门槛,必须先绕过才能进行后续操作;服务端sign算法是核心,破解后才能自由调用接口。
1.2 核心技术栈选型
| 工具 | 用途 | 优势 |
|---|---|---|
| Frida 16.2.1 | 动态Hook、内存注入 | 无需重新编译APP,运行时修改代码逻辑 |
| Objection 1.11.0 | Frida可视化工具 | 一键Hook常用函数,快速定位目标 |
| jadx-gui 1.4.7 | 静态反编译 | 将dex文件反编译为Java代码,可读性高 |
| IDA Pro 8.3 | 动态调试so库 | 破解native层加密算法 |
| Python 3.10 | 算法还原与接口调用 | 语法简单,库丰富,适合快速开发 |
二、完整破解流程总览
整个破解过程分为5个核心步骤,按顺序执行即可:
三、第一步:环境搭建与静态分析
3.1 环境准备
- 手机端:准备一台ROOT过的安卓手机(推荐安卓10-12,兼容性最好),或者使用雷电模拟器开启ROOT权限
- 电脑端:安装Python、Frida、Objection、jadx-gui
- 安装Frida服务端:下载对应架构的frida-server,推送到手机并运行
# 电脑端安装Frida和Objectionpipinstallfrida==16.2.1 frida-tools==12.2.1objection==1.11.0# 推送frida-server到手机adb push frida-server-16.2.1-android-arm64 /data/local/tmp/# 手机端运行frida-serveradb shellsuchmod755/data/local/tmp/frida-server-16.2.1-android-arm64 /data/local/tmp/frida-server-16.2.1-android-arm64&# 验证连接frida-ps-U3.2 静态分析APK
用jadx-gui打开目标APK,先做初步的静态分析,寻找签名验证相关的关键词:
- 搜索
"signature"、"sign"、"md5"、"sha1"、"getPackageInfo" - 搜索
"checkSignature"、"verifySignature"、"validateSign" - 查看AndroidManifest.xml,找到主Activity和Application类
关键技巧:优先查看Application类的onCreate方法,绝大多数APP的签名校验都在这里执行,APP一启动就会校验,失败直接闪退。
四、第二步:Frida Hook绕过客户端签名校验
这是最基础也是最关键的一步。如果不绕过客户端签名校验,你修改后的APK一运行就会闪退,根本无法进行后续的抓包和调试。
4.1 客户端签名校验的原理
安卓系统中,每个APK都有一个唯一的签名,APP可以通过PackageManager获取自身的签名哈希,然后和硬编码在代码中的正确哈希对比,如果不一致,说明APK被篡改了。
核心代码如下:
PackageInfopackageInfo=getPackageManager().getPackageInfo(getPackageName(),PackageManager.GET_SIGNATURES);Signature[]signatures=packageInfo.signatures;StringsignHash=MD5(signatures[0].toByteArray());if(!signHash.equals("正确的签名哈希")){System.exit(0);// 直接闪退}4.2 通用Hook绕过脚本
我们只需要HookPackageManager的getPackageInfo方法,让它永远返回正确的签名哈希即可。这是一个通用的脚本,适用于绝大多数APP。
// bypass_signature.jsJava.perform(function(){console.log("[*] 开始Hook签名校验...");// Hook PackageManager.getPackageInfo方法varPackageManager=Java.use("android.content.pm.PackageManager");PackageManager.getPackageInfo.implementation=function(packageName,flags){varpackageInfo=this.getPackageInfo(packageName,flags);// 如果是获取自身签名if(packageName==="com.target.app"&&(flags&0x40)!==0){console.log("[+] 拦截到签名获取请求");// 替换为正确的签名哈希(原APK的签名)varcorrectSign=Java.use("android.content.pm.Signature").$new(java.util.Base64.getDecoder().decode("原APK的签名Base64编码"));packageInfo.signatures.value=[correctSign];}returnpackageInfo;};console.log("[*] 签名校验Hook成功");});4.3 运行脚本验证
# 运行Frida Hook脚本frida-U-fcom.target.app-lbypass_signature.js --no-pause如果APP正常启动,没有闪退,说明客户端签名校验已经成功绕过。
进阶技巧:如果APP有多重签名校验,或者在so层做了校验,可以使用Objection的一键绕过功能:
objection-gcom.target.app run android root disable objection-gcom.target.app run android sslpinning disable objection-gcom.target.app run android signature disable五、第三步:定位并逆向服务端sign算法
绕过客户端校验后,我们就可以正常抓包了。接下来需要找到生成sign参数的加密函数,然后还原它的算法逻辑。
5.1 定位加密函数
- 抓包获取sign参数:用Charles或Fiddler抓包,找到包含sign参数的请求
- jadx全局搜索:搜索
"sign="、"&sign"、"sign:",找到生成sign的代码位置 - Frida Hook验证:在可疑的函数处打上Hook,打印参数和返回值,和抓包得到的sign对比,如果一致,说明找到了正确的加密函数
示例Hook脚本:
// find_sign.jsJava.perform(function(){console.log("[*] 开始Hook sign生成函数...");// Hook可疑的加密函数varSignUtils=Java.use("com.target.app.utils.SignUtils");SignUtils.generateSign.implementation=function(params){console.log("[+] 输入参数:"+params);varresult=this.generateSign(params);console.log("[+] 生成的sign:"+result);returnresult;};});5.2 动态调试还原算法
找到加密函数后,我们需要单步调试,分析它的算法逻辑。绝大多数APP的sign算法都是基于MD5或HMAC的,流程如下:
- 将请求参数按key的ASCII码升序排序
- 拼接成
key1=value1&key2=value2的字符串 - 拼接一个硬编码的盐值
- 进行MD5或HMAC加密,转成32位小写字符串
5.3 Python还原算法
根据调试得到的算法逻辑,用Python实现sign生成:
importhashlibimporttimeimportrandomdefgenerate_sign(params:dict,salt:str="app_secret_2026_abcdef")->str:""" 生成APP接口的sign参数 :param params: 请求参数字典 :param salt: 硬编码盐值 :return: 32位MD5 sign值 """# 1. 按key的ASCII码升序排序sorted_items=sorted(params.items(),key=lambdax:x[0])# 2. 拼接成key=value的字符串sign_str="&".join([f"{k}={v}"fork,vinsorted_items])# 3. 拼接盐值sign_str+=salt# 4. MD5加密,转小写returnhashlib.md5(sign_str.encode("utf-8")).hexdigest().lower()# 测试if__name__=="__main__":params={"userId":"123456","timestamp":str(int(time.time())),"nonce":''.join(random.choices("0123456789abcdef",k=16))}sign=generate_sign(params)print(f"生成的sign:{sign}")六、第四步:Python一键调用接口
现在我们已经可以自己生成sign参数了,接下来就可以用Python编写接口调用脚本,自由获取APP的数据。
importrequestsimporttimeimportrandomimportjson# 导入上面写的generate_sign函数fromsignimportgenerate_signclassTargetAppClient:def__init__(self):self.session=requests.Session()self.base_url="https://api.targetapp.com/v1"self.headers={"User-Agent":"TargetApp/2.6.0 (Android 12; SM-G9980)","Content-Type":"application/x-www-form-urlencoded"}defget_user_info(self,user_id:str)->dict:"""获取用户信息"""params={"userId":user_id,"timestamp":str(int(time.time())),"nonce":''.join(random.choices("0123456789abcdef",k=16))}params["sign"]=generate_sign(params)response=self.session.post(f"{self.base_url}/user/info",data=params,headers=self.headers,timeout=10)returnresponse.json()defget_video_list(self,user_id:str,page:int=1)->dict:"""获取用户视频列表"""params={"userId":user_id,"page":str(page),"pageSize":"20","timestamp":str(int(time.time())),"nonce":''.join(random.choices("0123456789abcdef",k=16))}params["sign"]=generate_sign(params)response=self.session.post(f"{self.base_url}/video/list",data=params,headers=self.headers,timeout=10)returnresponse.json()if__name__=="__main__":client=TargetAppClient()user_info=client.get_user_info("123456")print(json.dumps(user_info,ensure_ascii=False,indent=2))video_list=client.get_video_list("123456",page=1)print(json.dumps(video_list,ensure_ascii=False,indent=2))七、进阶:绕过高级签名防护
上面的方法适用于普通APP,但一些大型APP会有更高级的签名防护,这里分享几个常见问题的解决方案:
7.1 绕过Frida检测
很多APP会检测Frida的存在,一旦发现就会闪退。解决方案:
- 使用Frida的
--hide参数隐藏进程 - 修改frida-server的端口和名称
- 使用Objection的
--disable-analytics参数 - 使用frida-unpin工具绕过SSL Pinning和Frida检测
7.2 破解so层加密算法
如果sign算法是在so层实现的,无法通过Java层Hook获取,需要用IDA Pro动态调试so库:
- 用jadx找到调用so库的Java方法
- 用IDA Pro打开对应的so文件,找到导出函数
- 手机端运行frida-server,IDA远程附加调试
- 在加密函数处打上断点,单步调试分析汇编代码
7.3 绕过时间戳和nonce校验
很多APP会校验时间戳的有效性,误差不能超过5分钟,同时nonce不能重复。解决方案:
- 同步电脑和手机的时间
- 每次请求生成一个新的随机nonce
- 不要重复使用同一个nonce
八、踩坑实录:90%的人都会遇到的问题
- Frida连接失败:确保手机和电脑在同一个局域网,frida-server版本和电脑端Frida版本完全一致
- Hook不生效:检查类名和方法名是否正确,注意内部类的写法(
com.target.app.utils.SignUtils$InnerClass) - 算法还原错误:仔细对比参数排序和拼接顺序,注意空格和特殊字符的处理
- 接口返回403:检查sign是否正确,时间戳是否过期,headers是否完整
- APP闪退:可能还有其他签名校验没有绕过,用Frida Hook所有可能的签名相关函数
九、总结
APP签名验证的破解,本质上是一个攻防博弈的过程。没有永远无法破解的APP,只有不够努力的逆向工程师。
本文分享的这套方案,是目前最通用、最有效的签名验证破解方法。它不需要复杂的硬件和软件,只要掌握Frida Hook和基本的逆向知识,就能快速上手。
最后再次强调:技术本身没有对错,关键在于如何使用。请务必遵守法律法规,不要破解他人的APP用于非法用途,不要侵犯他人的知识产权。
👉 点击我的头像进入主页,关注专栏第一时间收到更新提醒,有问题评论区交流,看到都会回。
