Android不信任证书导致无法抓包的解决办法
Posted 一个初学的萌新
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android不信任证书导致无法抓包的解决办法相关的知识,希望对你有一定的参考价值。
前言
众所周知,android7.0以后系统不在信任用户的证书.这一改动使得我们在抓包的时候产生了诸多不便。在Android端很多人的做法是先刷入Magsik在通过Magsik模块的方式来将抓包证书修改成系统证书。
比较出名的就是大名鼎鼎的HttpCanary(小黄鸟),只是它虽然抓包方便,调试起来却没有Fiddler Charles等抓包工具方便。
接下来,我将介绍几种方法能让你像装了Magsik模块的小黄鸟一样方便的抓包,并且方便的调试,希望能对你有所帮助。
1.懒人专用法
Android系统对证书的不信任是从高版本开始的,那直接用低版本的Android系统即可完美解决这一问题。如果你手头没有低版本的安卓真机进行调试,可以尝试用PC端的Android模拟器来解决。诸如夜神(Nox),MuMu等等,绝大多数的模拟器都提供了Android5.0版本等低Android版本的系统。
在使用这些低版本的系统进行抓包时,除了APP使用上可能不如高版本流畅,在抓包这一方面效率绝对是相当的不错。
注意 --> 某些APP由于不支持32位可能无法运行。
2.逆向破解法
此法虽然名字高大上,实际上比较针对于自己写APP进行调试时使用。不过对于一些冷门的小软件或者是没那么注重安全的开发者所作出的产品来说,可能会有奇效。
⑴. 让apk的targetSDKVersion <= 23 即可解决. 碰到做了反编译等保护的很难实现
⑵. 在res/xml目录下添加network_security_config.xml文件,并在application下设置好如下属性:
android:networkSecurityConfig="@xml/network_security_config"
xml文件模板:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 设置允许http明文传输-->
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<!-- 设置信任系统级别证书-->
<certificates src="system" />
<!-- 设置信任用户自定义证书-->
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
3.移动证书法
本文的重点方法,通过将证书移动至系统证书目录,来解决不信任证书造成的无法抓包问题。
(1). 导出证书
这里根据你的抓包工具来自行导出,但是你需要注意的是,现阶段的Android有效时间超过两年的证书。所以你导出的来的证书最好有效期不要超过这个时长,以避免不必要的麻烦。
此处以Fiddler为例,我们使用Fiddler的证书制作工具来导出一个证书。
(2).转换证书格式
使用OpenSSL对cer证书进行格式转换,变为pem格式
openssl x509 -inform DER -in FiddlerRoot.cer -out FiddlerRoot.pem
注意自己替换证书路径
在转换成pem格式后查看证书的hash值
openssl x509 -inform PEM -subject_hash_old -in FiddlerRoot.pem
如图所示:
记住图中圈出来的hash值,这个hash值就是最后转换出来的文件的文件名。
此时我们直接将转换好的pem证书进行重命名即可,如果你想用命令的话,Windows下可以用ren命令
ren FiddlerRoot.pem e5c3944b.0
如果你在Linux操作或者Android中使用终端模拟器的话就用mv命令
最后你将获得一个e5c3944b.0文件
(3).推送证书
将转换好的证书推送到Android的 /system/etc/security 目录下并赋予文件可读权限即可,这个目录就是系统证书的目录。
推送方法1:使用adb进行推送
首先Android端打开adb调试,这里我是用的网络调试,也可以自己接线,如果是模拟器的话,也可以用模拟器的办法连接模拟器的adb。
PC端使用adb命令连接设备
首先与设备进行配对
adb pair 192.168.2.43:41053
随后连接设备
adb connect 192.168.2.43:37877
最后进入shell环境
adb shell
注意上面连接设备时输入的ip和端口,是与设备截图中一一对应的。
连接好设备后,来到目标目录
cd /system/etc/security
注意这里的目录权限,是755
而我们想要往目录中存放文件需要写的权限,此处采用adb临时赋予777
权限
chmod 777 cacerts
这里可能会失败,由于我是真机且已经获取了root,所以直接su
获取权限给目录加上了777
如果你是带root的模拟器,一般不需要专门su获取权限
如果碰到提示Read-only file system,可以使用mount -o remount,rw /system
,将系统文件夹挂载为可读写。然后再用chmod赋予777权限。
修改好权限后,输入exit
退出shell(如果你su了需要两次exit才能退出)。再用adb命令来推送证书文件
adb push E:\\Desktop\\e5c3944b.0 /system/etc/security/cacerts
这里的证书文件目录记得自己替换
如果你是模拟器的话,经过上边的操作应该已经推送成功了,不过我这里是真机所以无法直接推送到/system下,所以我这里稍微绕一下,先推送到手机内存中,然后再进入shell转移到cacerts目录下
adb push E:\\Desktop\\e5c3944b.0 /sdcard/
su
mv /sdcard/e5c3944b.0 /system/etc/security/cacerts
这里还要检查下你把文件推送过去,一定要给读的权限,不然在已信任的证书中是看不到这个证书的
chmod 666 e5c3944b.0
同样是真机的话记得推送完文件以后把目录的权限改回755
推送方法2:Root权限直接转移
既然你已经有了root权限,完全可以通过软件直接在Android端直接进行转移
图示为使用MT管理器直接将证书移动到系统证书目录中
总结:
不管你用什么办法,只要把证书文件移动到/system/etc/security/cacerts
这个目录下,并赋予证书文件可读的权限即可.
4.全局代理法
这个办法我并没有试验过,不过据说雷电模拟器是可行的
adb shell settings put global http_proxy <代理ip>:<代理端口>
参考文章:
https://blog.csdn.net/qq_43278826/article/details/124291040
安卓手机mitmproxy抓包
抓包#
Android 从 7.0 开始,系统不再信任用户 CA 证书(应用 targetSdkVersion >= 24 时生效,如果 targetSdkVersion <24 即使系统是 7.0 + 依然会信任)。只要证书不被信任就会导致我们添加中间人代理后,https请求时无法正常进行的。
有些app为了防止抓包可能会采用公共证书固定的手段进行防御,公证书固定(Certificate Pinning)是指 Client 端内置 Server 端真正的公钥证书。在 HTTPS 请求时,Server 端发给客户端的公钥证书必须与 Client 端内置的公钥证书一致,请求才会成功。
想要在高版本抓包,首先要解决的问题是如何将mitmproxy证书安装为系统证书。
想要安装系统证书,那么我们的安卓手机必须要满足的条件就是root。
想要解决公共证书固定的问题,可以通过LSPosed框架安装JustTrustMe插件解决,LSPosed框架是一款优秀的android java层 hook 框架,而JustTrustMe插件的作用是将 APK 中所有用于校验 SSL 证书的 API 都进行了 Hook,从而绕过证书检查。想要安装这个框架以及插件,前提条件也是root。
本文是要说明android7.0之后的版本如何进行抓包,以及遇到公共证书固定如何处理才能看到数据。
抓包可以使用真机,也可以使用虚拟机,这里假设手机已经root, 这里不说明如何进行root。
抓包原理
mitmproxy抓包原理是采用中间人的方式MITM
应用安装
mitmproxy - an interactive HTTPS proxy
我们可以到官网下载安装,也可以使用Python pip进行安装,我使用的python版本是3.10.1
,pip安装如下
pip install mitmproxy
我们安装完成后有3个命令可用
- mitmproxy:是一个控制台工具,允许交互式检查和修改 HTTP 流量;
- mitmweb:mitmproxy 是基于 Web 的用户界面,它允许交互式检查和修改 HTTP 流量;
- mitmdump:mitmproxy 的命令行版本。它提供了类似 tcpdump 的功能,可查看、记录和以编程方式转换 HTTP 流量。
使用pip安装,我们在编写扩展脚本的时候比较方便,比如我们可以捕获请求修改返回值。
mitmproxy证书安装为系统证书
打开mitmweb, 然后将我们的电脑代理设置为127.0.0.1:8080
浏览器打开mitm.it
网址下载安卓的ca证书
然后在下载目录打开命令行, 执行以下命令获取hash
openssl x509 -subject_hash_old -in mitmproxy-ca-cert.cer
openssl只要安装了git在安装根目录下的
usr/bin
中会有这个命令,我们可以将这个加入到path变量中
然后将mitmproxy-ca-cert.cer文件重命名为c8750f0d.0
(这个根据证书计算的hash来命名,.
后面的是序号,一般填写0即可,如果证书hash有冲突可以增加这个序号来解决冲突,例如1)
然后通过adb我们来安装证书, 请确保手机打开开发者模式,并打开usb调试,以及插上了usb连接线
# 证书文件传到手机根目录
adb push c8750f0d.0 /sdcard/
# adb打开手机端的shell
adb shell
# 切换到root账号,这个时候会和手机的root管理器申请root
su root
# 重新挂载为已经挂载了的文件系统(以读写权限挂载)
mount -o rw,remount /system
# 将证书移动到系统ca证书目录
mv /sdcard/c8750f0d.0 /system/etc/security/cacerts/
# 设置证书的权限
chmod 644 /system/etc/security/cacerts/c8750f0d.0
# 重新启动手机
reboot
LSPosed框架以及插件安装
我的手机版本情况
需要先安装magisk面具,手机root要通过magisk,我这里测试的手机是这样做的。
Releases · topjohnwu/Magisk (github.com)
面具如何安装请参考:小米8root
Releases · RikkaApps/Riru (github.com) 这里选择版本 riru-v26.1.6.r527.cdcb9f34c6-release.zip
LSPosed/LSPosed: LSPosed Framework (github.com)这里选择版本LSPosed-v1.8.3-6552-riru-release.zip
lsposed有两种方式riru方式,还有就是zygisk方式,建议第二种,不过这里安装时用的riru, 使用zygisk不用安装riru模块
xposed 、edxposed、lsposed都是在不同的安卓版本下给系统增加钩子来改变代码执行逻辑的解决方案,这里用的时miui12.5,对于软件对版本的支持情况,这里暂不做说明。
lsposed安装后会自动安装管理器
Releases · Fuzion24/JustTrustMe (github.com) 版本选择v2
下载安装JustTrustMe,然后在LSPosed管理器中启用这个模块
MagiskHide的作用是避免一些应用检测root权限,magisk开发者在最新的24版本移除了magiskhide,
但是新增的 Zygisk 排除列表用另一种方式真正地保证了应用运行环境的完整性,比起以往 MagiskHide 与应用开发者检测手段「斗智斗勇」的情形来说,通过排除这种方式隔离模块作用范围,是一种将选择交给用户、同时与应用开发者互相信任的新思路。
应用抓包
运行mitmweb命令,会打开一个web的网页查看请求情况
使用ipconfig查看主机ip
找到手机连接的wifi, 打开wifi设置,将代理修改为手动,然后填写主机ip和mitmproxy的监听端口即可
然后我们打开应用就能看到请求了
编写脚本扩展
我们可以通过编写mitmproxy支持的扩展脚本,来根据请求进行修改返回参数等操作,模板如下
from mitmproxy import http, ctx
import json
class xxx:
def xxx:
def xxx
addons = [
xxx() //类名的加载,也可以定义多个类,然后以数组的形式添加,进行加载
]
class类中的方法名其实就是mitmproxy支持的事件,这里只提两个
- request 请求拦截
- response 响应拦截
def request(self, flow:http.HTTPFlow):
# 逻辑代码
def response(self, flow:http.HTTPFlow):
# 逻辑代码
这里有一个破解app加密规则,根据加密规则解析响应,然后将判断依据改为true来跳过验证的例子
aes.py
需要
pycryptodome
库
# https://zhuanlan.zhihu.com/p/365008686
# 根据这个地址提供的类进行简单修改,供其它脚本使用
from Crypto.Cipher import AES
import base64
class AESTool:
def __init__(self, secret_key):
self.key = secret_key.encode('utf-8')
self.iv = secret_key.encode('utf-8')
def pkcs7padding(self, text):
"""
明文使用PKCS7填充
"""
bs = 16
length = len(text)
bytes_length = len(text.encode('utf-8'))
padding_size = length if (bytes_length == length) else bytes_length
padding = bs - padding_size % bs
padding_text = chr(padding) * padding
self.coding = chr(padding)
return text + padding_text
def aes_encrypt(self, content):
"""
AES加密
"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
# 处理明文
content_padding = self.pkcs7padding(content)
# 加密
encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
# 重新编码
result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
return result
def aes_decrypt(self, content):
"""
AES解密
"""
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
content = base64.b64decode(content)
text = cipher.decrypt(content).decode('utf-8')
return self.pkcs7padding(text)
cltool.py
import aes
from mitmproxy import http,ctx
import json
class Modify:
def response(self, flow:http.HTTPFlow):
if flow.request.url.startswith("https://app.example.aa"):
# 这是app的加密规则,需要解析出来进行修改
authorization = flow.request.headers["Authorization"]
secret_key = authorization[2:18]
aes_tool = aes.AESTool(secret_key)
r = json.loads(flow.response.get_text())
enc_data = r["encData"]
data:str = aes_tool.aes_decrypt(enc_data)
# enc_data解码后有不可见字符,需要剔除才可以正常的json操作
clear_data = "".join(x for x in data if x.isprintable())
# 处理过后的data放到header进行展示方便调试
# 这样我们可以在mitmweb web页面中的Header选项中看到明文,方便我们进行调试
flow.response.headers["data"] = clear_data
# 如果请求时登录请求将用户状态修改vip和svip
if flow.request.url.startswith("https://app.example.aa/api/user/login"):
data_obj = json.loads(clear_data)
data_obj["vipStatus"] = 1
data_obj["svip"] = True
enc_data = aes_tool.aes_encrypt(json.dumps(data_obj))
r["encData"]=enc_data
flow.response.set_text(json.dumps(r))
return
# 如果是获取当前登录状态的将用户状态修改vip和svip
if flow.request.url.startswith("https://app.example.aa/api/user/base/info"):
data_obj = json.loads(clear_data)
data_obj["vipStatus"] = 1
data_obj["svip"] = True
enc_data = aes_tool.aes_encrypt(json.dumps(data_obj))
r["encData"]=enc_data
flow.response.set_text(json.dumps(r))
return
# 如果请求是watch,这个是判断金币视频有没有查看权限的,只要将canWatch改为true就可以正常跳过认证
if flow.request.url.startswith("https://app.example.aa/api/video/can/watch"):
data_obj = json.loads(clear_data)
data_obj["canWatch"] = True
enc_data = aes_tool.aes_encrypt(json.dumps(data_obj))
r["encData"]=enc_data
flow.response.set_text(json.dumps(r))
return
addons = [
Modify()
]
参考链接
(33条消息) JustTrustMe 原理分析_tangsilian的博客-CSDN博客_justtrustme
(33条消息) Android 高版本 HTTPS 抓包解决方案及问题分析!_承香墨影的博客-CSDN博客
(33条消息) JustTrustMe 原理分析_tangsilian的博客-CSDN博客_justtrustme
Android 玩机「神器」的矛盾与新生:Magisk Canary 更新详解 - 少数派 (sspai.com)
python AES 加密 (CBC pkcs7padding 128) - 知乎 (zhihu.com)
python如何移除所有不可见字符 - web开发 - 亿速云 (yisu.com)
(33条消息) mitmproxy使用(二)-自定义脚本编写_chuhan_19930314的博客-CSDN博客_mitmproxy脚本
以上是关于Android不信任证书导致无法抓包的解决办法的主要内容,如果未能解决你的问题,请参考以下文章
把charles,Fiddler 证书安装到android根目录,解决android7.0以上抓包无网络问题