Android 7以上版本抓包https
背景:
相信很多测试小伙伴发现,app安装在Android7以下的设备上使用fiddler或charles工具可以正常抓包https,但安装在Android7以上的设备上就无法抓包成功,造成测试bug定位困难。
前言:
当项目开发人员完成开发需求后,会直接给我们测试人员一个测试apk,为了不麻烦开发人员,我们需要自己利用相关工具重新编译这个apk,才能在android7以上的设备上进行抓包https。
一、工具准备:
1.下载和安装Java开发工具包(JDK)。安装之后,配置环境变量,APKTool、zipalign、apksigner 和 keytool 需要预先安装,并在系统路径中可用
liunx系统安装:
wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip unzip commandlinetools-linux-11076708_latest.zip -d cmdline-tools mkdir --parents "$ANDROID_HOME/cmdline-tools/latest" sudo mv cmdline-tools/* "$ANDROID_HOME/cmdline-tools/latest/" export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
windows安装暂不作说明,自行在网上找教程安装。
二、生成签名文件
如果能直接找开发人员拿到签名文件,就可以忽略该步骤
cmd doc窗口执行:
keytool -genkey -v -keystore my-release-key.keystore -alias dm -keyalg RSA -keysize 2048 -validity 10000
输入2次相同密码,再按六次回车,最后一次输入:y

三、在电脑中新建一个空的文件夹,文件夹命名不要有中文
1 把步骤1生成的签名文件my-release-key.keystore放在该文件夹下
2 把下列python脚本放在该文件夹中,用编译器打开,import导入代码中需要的相关工具包
3 把代码中
/home/dm/work/apk/my-release-key.keystore 改为自己存放my-release-key.keystore的路径
/home/dm/work/apk 改为开发人员给的apk存放的目录路径
1 import os 2 import subprocess 3 import logging 4 import tkinter as tk 5 from tkinter import filedialog, messagebox 6 from tkinter.ttk import Progressbar 7 import shutil 8 9 # 配置日志 10 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 11 12 13 class APKToolGUI: 14 def __init__(self, root): 15 self.root = root 16 self.root.title("APK 处理工具") 17 self.root.geometry("400x300") 18 19 self.file_paths = [] 20 self.keystore_path = "/home/dm/work/apk/my-release-key.keystore" 21 self.default_apk_path = "/home/dm/work/apk" 22 23 # 文件选择按钮 24 self.select_button = tk.Button(root, text="选择 APK 文件", command=self.select_files) 25 self.select_button.pack(pady=10) 26 27 # 显示选中文件 28 self.file_label = tk.Label(root, text="") 29 self.file_label.pack(pady=10) 30 31 # 选择密钥库按钮 32 self.keystore_button = tk.Button(root, text="选择密钥库", command=self.select_keystore) 33 self.keystore_button.pack(pady=10) 34 35 # 显示密钥库路径 36 self.keystore_label = tk.Label(root, text=f"已选择密钥库: {self.keystore_path}") 37 self.keystore_label.pack(pady=10) 38 39 # 确定按钮 40 self.confirm_button = tk.Button(root, text="确定", command=self.start_process, state=tk.DISABLED) 41 self.confirm_button.pack(pady=10) 42 43 # 进度条 44 self.progress = Progressbar(root, orient=tk.HORIZONTAL, length=300, mode='determinate') 45 self.progress.pack(pady=10) 46 47 # 绑定关闭窗口事件 48 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) 49 50 # 默认选中第一个 APK 文件 51 self.load_default_apk() 52 53 def load_default_apk(self): 54 default_path = self.default_apk_path 55 # 清空当前文件路径 56 self.file_paths = [] 57 58 # 获取目录下的所有 APK 文件 59 apk_files = [f for f in os.listdir(default_path) if f.endswith('.apk')] 60 if apk_files: 61 # 选择第一个 APK 文件 62 self.file_paths = [os.path.join(default_path, apk_files[0])] 63 self.file_label.config(text=f"已选择文件: {self.file_paths[0]}") 64 self.confirm_button.config(state=tk.NORMAL) 65 else: 66 self.file_label.config(text="") 67 self.confirm_button.config(state=tk.DISABLED) 68 69 # 强制更新界面 70 self.root.update_idletasks() 71 72 def select_files(self): 73 default_path = self.default_apk_path 74 self.root.update_idletasks() # 强制更新界面 75 selected_files = filedialog.askopenfilenames(initialdir=default_path, filetypes=[("APK Files", "*.apk")]) 76 if selected_files: 77 self.file_paths = list(selected_files) 78 self.file_label.config(text=f"已选择文件: {', '.join(self.file_paths)}") 79 self.confirm_button.config(state=tk.NORMAL) 80 81 def select_keystore(self): 82 self.keystore_path = filedialog.askopenfilename(initialdir="/home/dm/work/apk", 83 filetypes=[("Keystore Files", "*.keystore")]) 84 if self.keystore_path: 85 self.keystore_label.config(text=f"已选择密钥库: {self.keystore_path}") 86 87 def start_process(self): 88 if not self.keystore_path: 89 messagebox.showerror("错误", "请先选择密钥库文件!") 90 return 91 92 self.confirm_button.config(state=tk.DISABLED) 93 94 for apk_file in self.file_paths: 95 self.process_apk(apk_file) 96 97 self.confirm_button.config(state=tk.NORMAL) 98 self.load_default_apk() # 处理完所有文件后刷新默认选项 99 100 def process_apk(self, apk_file): 101 output_dir = os.path.splitext(os.path.basename(apk_file))[0] 102 unsigned_apk = f"{output_dir}_unsigned.apk" 103 signed_apk = f"{output_dir}_signed.apk" 104 105 try: 106 self.update_progress(10, "解压 APK 文件") 107 self.decompile_apk(apk_file, output_dir) 108 self.update_progress(30, "修改 APK 文件") 109 self.modify_apk(output_dir) 110 self.update_progress(50, "重新编译 APK 文件") 111 self.recompile_apk(output_dir, unsigned_apk, signed_apk) 112 self.update_progress(70, "优化 APK 文件") 113 self.optimize_apk(unsigned_apk, signed_apk) 114 self.update_progress(90, "签名 APK 文件") 115 self.sign_apk(signed_apk) 116 self.update_progress(100, "处理完成") 117 messagebox.showinfo("成功", f"APK 文件处理完成:{apk_file}") 118 119 # 删除 unsigned_apk 文件和手动选择的 apk 文件 120 self.delete_file(unsigned_apk, "未签名的 APK 文件") 121 self.delete_file(apk_file, "原始选择的 APK 文件") 122 123 # 删除编译出来的文件夹中的所有内容 124 self.delete_folder_contents(output_dir, "编译出来的文件夹") 125 except Exception as e: 126 messagebox.showerror("错误", f"处理过程中发生错误: {str(e)}") 127 logging.error(f"处理过程中发生错误: {str(e)}") 128 129 def update_progress(self, value, message=""): 130 self.progress['value'] = value 131 self.root.update_idletasks() 132 logging.info(message) 133 134 def decompile_apk(self, apk_file, output_dir): 135 # subprocess.run(["apktool", "d", "-f", apk_file, "-o", output_dir], check=True) 136 subprocess.run(["apktool", "d", "-f", apk_file, "-o", output_dir], 137 check=True, stdout=subprocess.DEVNULL) 138 139 logging.info("APK 文件解压完成") 140 141 def modify_apk(self, output_dir): 142 manifest_path = os.path.join(output_dir, "AndroidManifest.xml") 143 with open(manifest_path, "r") as file: 144 manifest_content = file.read() 145 146 manifest_content = manifest_content.replace( 147 'android:extractNativeLibs="false"', 'android:extractNativeLibs="true"') 148 manifest_content = manifest_content.replace( 149 '<application', '<application android:networkSecurityConfig="@xml/network_security_config"') 150 151 with open(manifest_path, "w") as file: 152 file.write(manifest_content) 153 154 xml_dir = os.path.join(output_dir, "res/xml") 155 os.makedirs(xml_dir, exist_ok=True) 156 config_content = """<?xml version="1.0" encoding="utf-8"?> 157 <network-security-config> 158 <base-config cleartextTrafficPermitted="true"> 159 <trust-anchors> 160 <certificates src="user"/> 161 <certificates src="system"/> 162 </trust-anchors> 163 </base-config> 164 </network-security-config>""" 165 166 with open(os.path.join(xml_dir, "network_security_config.xml"), "w") as file: 167 file.write(config_content) 168 logging.info("APK 文件修改完成") 169 170 def recompile_apk(self, output_dir, unsigned_apk, signed_apk): 171 if os.path.exists(signed_apk): 172 os.remove(signed_apk) 173 logging.info(f"已删除旧的已签名 APK 文件: {signed_apk}") 174 subprocess.run(["apktool", "b", "-f", output_dir, "-o", unsigned_apk], check=True, stdout=subprocess.DEVNULL) 175 logging.info("APK 文件重新编译完成") 176 177 def optimize_apk(self, unsigned_apk, signed_apk): 178 subprocess.run(["zipalign", "-v", "4", unsigned_apk, signed_apk], check=True, stdout=subprocess.DEVNULL) 179 logging.info("APK 文件优化完成") 180 181 def sign_apk(self, signed_apk): 182 key_password = "123456" 183 try: 184 subprocess.run([ 185 "apksigner", "sign", 186 "--v2-signing-enabled", "false", 187 "--ks", self.keystore_path, 188 "--ks-pass", f"pass:{key_password}", 189 signed_apk 190 ], check=True, stdout=subprocess.DEVNULL) 191 logging.info("APK 文件签名完成") 192 except subprocess.CalledProcessError as e: 193 raise RuntimeError(f"APK 签名失败: {e}") 194 195 def delete_file(self, file_path, file_description): 196 if os.path.exists(file_path): 197 try: 198 os.remove(file_path) 199 logging.info(f"{file_description} 已删除: {file_path}") 200 except Exception as e: 201 messagebox.showerror("错误", f"无法删除 {file_description}: {str(e)}") 202 logging.error(f"无法删除 {file_description}: {str(e)}") 203 else: 204 logging.warning(f"{file_description} 不存在: {file_path}") 205 206 def delete_folder_contents(self, folder_path, folder_description): 207 if os.path.exists(folder_path): 208 try: 209 shutil.rmtree(folder_path) 210 logging.info(f"{folder_description} 中的所有内容已删除: {folder_path}") 211 except Exception as e: 212 messagebox.showerror("错误", f"无法删除 {folder_description} 中的内容: {str(e)}") 213 logging.error(f"无法删除 {folder_description} 中的内容: {str(e)}") 214 else: 215 logging.warning(f"{folder_description} 不存在: {folder_path}") 216 217 def on_closing(self): 218 pid = os.getpid() 219 os.kill(pid, 9) 220 self.root.destroy() 221 222 223 if __name__ == "__main__": 224 root = tk.Tk() 225 app = APKToolGUI(root) 226 root.mainloop()
四、执行代码,会弹出一个GUI界面,直接点击确定即可运行生成一个新的APK,新的APK和代码同一个目录下,该apk安装在手机上可以用fiddler或charles工具抓包。

浙公网安备 33010602011771号