一、背景
描述:
接近年底,工作不是很忙,刚好朋友找我问下,能不能抓取安卓apk的的一些接口数据,进行一些数据分析,(这里仅对公开数据抓取,并不涉及篡改接口数据等操作)。寻思着,早些年做过安卓开发,以前玩游戏的时候也一直想自己做个脚本自己玩,遂花了几天时间研究了下,目前世面上的一些主流抓包工具以及方法,一番尝试操作下来,虽然从技术层面来看不难,但真的做完还是挺有意思的。
二、前期资料准备
1、抓包工具:
fiddler + 雷电模拟器(一款安卓模拟器)
2、后端数据分析:
springboot + Mybatis + mysql
3、抓包对象
xxk软件(一款语音社交软件)
三、抓包工具以及相关配置
📎fiddler adb.zip(fiddler以及adb工具)
写在前面:fiddler这块的操作,参考了csdn 上 青凯平 大佬的博客
原文链接如下: blog.csdn.net/weixin_4339...
整个流程大概是这样:
1、Fiddler设置
1.1、HTTPS:解密https流量、忽略不安全证书
1.2、连接设置:允许远程计算机连接
点击允许远程计算机连接,点确定保存
在桌面点击导出的证书进行下一步安装即可,全部默认一直点,点到导入成功为止。
Fiddler配置完成将其关闭重启 。
2、模拟器设置
2.1、网络设置:桥接驱动安装,及开启桥接
安装网桥驱动,出现安装成功即可。
安装完成后点保存设置自动重启。
2.2、WLAN设置:设置fiddler代理
打开模拟器设置-打开WiFi菜单-修改wifi配置(有些模拟器有小笔头修改键,有些模拟器是鼠标长按会出现"修改网络"大同小异,可自行参照,核心就是修改连接wifi的手动代理,ip写本机,端口写8888 对饮fiddler软件里面的,如果端口占用在fiddler里面修改此处改成一致即可)最后点击保存!
2.3、证书安装:物理机ip:8888
打开模拟器浏览器,地址栏输入本物理机ip跟上8888端口,就是输入代理ip和端口。会跳转证书安装页面。
为什么要这样安装证书1.简单便捷2.可以变相去检测与物理机的连接是否正常
图片有点长,耐心按步骤点击
2.4、设置信任证书:使用adb命令,将安装好的证书挪到系统目录中。
使用雷电模拟器的直接看
bash
原-用户证书目录:/data/misc/user/0/cacerts-added/
新-系统证书目录:/system/etc/security/cacerts/
如果不移动安装好的证书,此处会出现不信任的证书,频繁弹出(可以看到组织单位是我们安装fiddler)。
此处需要提前开启,模拟器的root权限,否则无法su 切换用户(具体因模拟器而异:有些root权限要在模拟器里面开启,有些不需要,遇到无法su的问题,找找模拟器原因)
2.5、报错分析
随即我们继续操作,将/data/misc/user/0/cacerts-added/的证书文件,移动到/system/etc/security/cacerts/,使其变为信任证书。
bash
# 进入adb shell 工具
adb shell
# 切换root用户
gracelte:/ $ su
# 查看用户证书安装的名称
127|gracelte:/ # ls /data/misc/user/0/cacerts-added/
e5c3944b.0
# 将用户证书挪至系统证书目录 /system/etc/security/cacerts/
gracelte:/ # mv /data/misc/user/0/cacerts-added/e5c3944b.0 /system/etc/security/cacerts/
mv: /system/etc/security/cacerts//e5c3944b.0: Read-only file system
报错1:
在移动证书到/system/etc/security/cacerts/ 会报出问题
bash
报错:mv: /system/etc/security/cacerts//e5c3944b.0: Read-only file system
报错原因:/system 是以只读的形式挂载上来的,所以无论你怎么chmod都无法修改其权限。
解决办法:重新挂载,挂载读写执行权限
ruby
gracelte:/ $ su
# 重新挂载/system 文件夹
:/ # mount -o rw,remount /system
mount: '/system' not in /proc/mounts
报错2:
bash
报错:mount: '/system' not in /proc/mounts
报错原因:在Android8.0以上通过以上mount命令,找不到/system代表的节点和拥有的权限信息,这是因为Android8.0之后谷歌加入了A/B system的特性,system分区跟ramdisk分区打包在一起,因此系统起来之后也就不存在system分区了,而是直接把system镜像挂载到/根目录上。所以/proc/mounts文件中没有包含/system的权限信息。
查看模拟器安卓版本:
ruby
# 进入adb shell 工具
adb shell
# 切换root用户
gracelte:/ $ su
# 查看安卓版本
gracelte:/ # getprop ro.build.version.release
9
解决办法:解锁磁盘,重新挂载
bash
# 在windows窗口运行以下命令,不要进到adb shell 里面运行
D:\platform-tools> adb root
# 解锁分区(需要升级到最新版本adb,才有disable-verity工具)
D:\platform-tools> adb disable-verity
# 重新挂载,没加权限 表示rwx全部挂上
D:\platform-tools> adb remount
报错3:
报错:如果是旧版本adb会显示如下信息
sql
networking:
adb ppp <tty> [parameters] - Run PPP over USB.
Note: you should not automatically start a PPP connection.
<tty> refers to the tty for PPP stream. Eg. dev:/dev/omap_csmi_tty1
[parameters] - Eg. defaultroute debug dump local notty usepeerdns
adb sync notes: adb sync [ <directory> ]
<localdir> can be interpreted in several ways:
- If <directory> is not specified, both /system and /data partitions will be updated.
- If it is "system" or "data", only the corresponding partition
is updated.
environmental variables:
ADB_TRACE - Print debug information. A comma separated list of the following values
1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp
ANDROID_SERIAL - The serial number to connect to. -s takes priority over this if given.
ANDROID_LOG_TAGS - When used with the logcat option, only these debug tags are printed.
原因:旧版本adb不含disable-verity 工具包
解决办法:安装最新adb工具
报错4:
报错:Failed to read fstab Maybe run adb root?
arduino
Failed to read fstab Maybe run adb root?
原因:雷电模拟器9把,这块直接界面化了,不需要通过disable-verity 解锁磁盘,但是其他种类模拟器会用到这个功能,大同小异,自己拿捏吧
解决办法:在模拟器设置,磁盘管理开启 system.vmdk可写入
解决雷电模拟器问题直接从这开始看,以上是报错处理,供其他模拟器参考分析。
perl
# 在windows窗口运行以下命令,不要进到adb shell 里面运行
D:\platform-tools> adb root
# 雷电模拟器,直接在设置打开磁盘写入功能
# 重新挂载,没加权限 表示rwx全部挂上
D:\platform-tools> adb remount
# 进入adb shell
D:platform-tools>adb shell
# 查看用户证书位置
gracelte:/ # ls /data/misc/user/0/cacerts-added/
e5c3944b.0
# 拷贝证书到目标文件夹
gracelte:/ # mv /data/misc/user/0/cacerts-added/e5c3944b.0 /system/etc/security/cacerts/
雷电模拟器,直接在设置打开磁盘写入功能
将以上配置完重启模拟器和fiddler就可以抓包了
四、后端服务搭建
基于第三步,apk的数据已经可以通过fiddler拦截并抓取到了,那么对于这一条一条请求数据我们该怎么进行提取并进行分析呢。
方案一:
这里博主一开始是通过自定义规则,对特定的url 进行拦截,然后将请求内容和响应内容写入到本地txt文件,
然后通过程序去读取txt 文件并解析,运行了一段时间后,发现fiddler 写入到本地的文件编码格式为 utf16El ,每次去转码另存为太麻烦,遂采用了第二种方案。
方案二:
在这里,主要也是通过fiddler的自定义规则,在 OnBeforeResponse 方法中,对于获取到的请求和响应,直接再次发送到我们自己的服务上,在我们自己的服务上对 json 数据进行解析,直接存入数据库(这里可以结合自己的需求对数据进行解析去重),说做就做!
1、fiddler 自定义规则
见上图,找到fiddler中的自定义规则,这边自定义规则,在我看来主要是一个c#的语法,与c#不同的是,在导包的时候,c#这边使用的是using, 自定义规则里面使用的是 import,至于这个脚本语言的编写,大家可以自定研究,博主这里主要针对我自己的需求提供下相关代码。
因为需要用到网络请求,所以这里我引用了c#的一些原生包,大家用到的话,可以在规则的头部加上这些
ini
import System;
import System.Windows.Forms;
import Fiddler;
import System.IO;
import System.Net;
import System.Text;
import System.Threading.Tasks;
import Newtonsoft.Json;
import System.Net.Http;
2、对fiddler的response 转发到自己的服务
博主这里主要是对response 数据进行二次请求到我们自己的服务,那么可以在 OnBeforeResponse中添加以下方法:
javascript
static function OnBeforeResponse(oSession: Session) {
if (m_Hide304s && oSession.responseCode == 304) {
oSession["ui-hide"] = "true";
}
if (oSession.fullUrl.Contains("api.chenlongtech.cn/v1/gift")){
try{
// 获取响应内容
var responseContent = oSession.GetResponseBodyAsString().replace(/"/g,"'");
// 地址替换成自己的服务地址
var newServiceUrl = "http://127.0.0.1:8080/uki/msg";
var webClient = new WebClient();
webClient.Headers["Content-Type"] = "application/json";
// 防止后端服务中文乱码
webClient.Headers["charset"] = "utf-8";
// 这行是自定义需求,博主需要拿到request的请求里面的时间,你们用不到可不加
webClient.Headers["x-timestamp"] = oSession.oRequest.headers["x-timestamp"];
// 防止后端服务中文乱码
webClient.Encoding = Encoding.UTF8;
var newServiceResponse = webClient.UploadString(newServiceUrl, "POST", responseContent);
}catch(e){
}
}
}
2.1、后端服务controller接收解析数据
这里的后端语言是java,在controller里面对上面转发的response 数据进行接收,这部分代码就简单贴一下,如果如要,后面博主可以传到gitee 上
kotlin
package com.see.you.controller;
import com.see.you.pojo.Result;
import com.see.you.service.UkiService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/uki")
public class UkiController {
@Resource
private UkiService ukiService;
/**
* 接收fiddler的response的数据
* @param json 原始json
* @param request req
* @return
*/
@PostMapping(value = "/msg", produces = "application/json;charset=UTF-8")
public Result<String> test(@RequestBody String json, HttpServletRequest request) {
String timestamp = request.getHeader("x-timestamp");
ukiService.handleJson(json, timestamp);
return Result.success("");
}
/**
* 先保存数据
* 再保存百分比
* 再清洗数据去重
*/
@GetMapping("/list")
public Result<String> parseGift() {
ukiService.listGift();
return Result.success("");
}
}
这边如果取到的json数据有问题,可能需要替换一下。
ini
json = json.replace('\'', '\"');
3、对fiddler的response 保存到本地
另外在规则里面,找到 OnBeforeResponse 该方法,如果你需要写入本地 txt 文件,可以添加以下代码
ini
// api.chenlongtech.cn/v1/gift 这里的地址可以替换你们需要拦截的地址
if (oSession.fullUrl.Contains("api.chenlongtech.cn/v1/gift")){
//消除保存的请求可能存在乱码的情况
oSession.utilDecodeResponse();
var fso;
var file;
fso = new ActiveXObject("Scripting.FileSystemObject");
//文件保存路径,可自定义
file = fso.OpenTextFile("D:\\logs\\gift.txt",8 ,true, true);
file.writeLine(oSession.GetResponseBodyAsString());
file.writeLine("\n");
file.close();
}
五、数据分析
数据通过后端服务已经落库,后面想怎么分析就怎么分析啦~
六、总结
蛮有意思的~