安卓手机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

image.png

应用安装

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

image.png

image.png

浏览器打开mitm.it网址下载安卓的ca证书

image.png

然后在下载目录打开命令行, 执行以下命令获取hash

openssl x509  -subject_hash_old -in mitmproxy-ca-cert.cer

image.png

Git - Downloads (git-scm.com)

openssl只要安装了git在安装根目录下的usr/bin中会有这个命令,我们可以将这个

加入到path变量中image.png

然后将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框架以及插件安装

我的手机版本情况

image.png

需要先安装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

Screenshot_2022-06-04-10-36-00-308_com.topjohnwu.magisk.jpgScreenshot_2022-06-04-10-35-52-871_com.miui.home.jpg

lsposed有两种方式riru方式,还有就是zygisk方式,建议第二种,不过这里安装时用的riru, 使用zygisk不用安装riru模块

xposed 、edxposed、lsposed都是在不同的安卓版本下给系统增加钩子来改变代码执行逻辑的解决方案,这里用的时miui12.5,对于软件对版本的支持情况,这里暂不做说明。

lsposed安装后会自动安装管理器

Releases · Fuzion24/JustTrustMe (github.com) 版本选择v2

下载安装JustTrustMe,然后在LSPosed管理器中启用这个模块

Screenshot_2022-06-04-10-35-45-562_com.android.shell.jpg

MagiskHide的作用是避免一些应用检测root权限,magisk开发者在最新的24版本移除magiskhide

但是新增的 Zygisk 排除列表用另一种方式真正地保证了应用运行环境的完整性,比起以往 MagiskHide 与应用开发者检测手段「斗智斗勇」的情形来说,通过排除这种方式隔离模块作用范围,是一种将选择交给用户、同时与应用开发者互相信任的新思路。

应用抓包

运行mitmweb命令,会打开一个web的网页查看请求情况

image.png

image.png

使用ipconfig查看主机ip

image.png

找到手机连接的wifi, 打开wifi设置,将代理修改为手动,然后填写主机ip和mitmproxy的监听端口即可

lADPJwY7TNQ-F3zNBQDNAkA_576_1280.jpg

然后我们打开应用就能看到请求了

image.png

编写脚本扩展

我们可以通过编写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脚本

posted @ 2022-06-04 10:50  Bug的梦魇  阅读(2158)  评论(0编辑  收藏  举报