点击蓝字,关注我们
日期:2022-07-21 作者:Zero 介绍:一份靠谱的移动安全测试环境搭建指南。 本文涉及的文件可以在公众号回复 0721-APP
获取,如果本文对您有帮助,来个点赞、在看就是对我们莫大的鼓励。
1、 前言
由于谷歌旗下的 Pixel
系列手机因其开放性以及对自家安卓系统的兼容非常好,所以很多移动安全人员都喜欢用该系列的手机作为测试机,进行日常应用测试及脱壳分析等工作,本文为刚接触移动安全的朋友提供一份靠谱的基础环境构建指南系列之Ⅲ-APP违规上报信息检测。
日常工作中,偶尔会碰到应用合规性检查的需求,比如在未告知用户情况下收集IMEI
、已安装应用列表
等个人隐私信息,目前最普遍的方式就是抓取APP
数据包,检测数据包中是否存在上述隐私信息,由于违规上报信息检测不需要对数据包进行修改、拦截操作,只需要有搜索过滤功能即可满足需求,在上文中已经完成初步的违规上报信息检测环境搭建,本文将在上篇文章基础上把检测过程进一步自动化,效果如下图所示。
2、 正文
相关信息:
-
手机型号:Google Pixel 2 无锁版
-
官方镜像版本:11.0.0 (RP1A.201005.004.A1, Dec 2020)
-
Magisk:v23.0
-
MagiskTrustUserCerts:v0.4.1
-
Drony:v1.3.154
-
Ubuntu:16.04.3 LTS
-
Node.js:v16.14.2
-
NPM:8.5.0
-
AnyProxy:4.1.3
注:本文涉及到的文件均已打包,开箱即用,获取方式见文末。
2.1 优化手动检测的体验
接上文继续,上文中提到官方版本的AnyProxy
搜索过滤的粒度略粗,需要自行调整代码以满足功能需求,因为官方代码中只对URL
参数做匹配,所以官方版本只能匹配出使用GET
方法在URL
中提交数据的请求,默认不对POST
提交过去的数据进行处理,而且还对大小写敏感,比如搜索MAC
字段,就需要把mac
、MAC
两种形式全部填入,在此场景下使用起来不是很方便,于是阅读源码看一下实现逻辑,首先通过页面上的文字定位到record-filter
这部分代码。
顺着逻辑寻找updateFilter
来自哪,在文件头部可以看到相关引用。
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import ClassBind from 'classnames/bind';
import { connect } from 'react-redux';
import { Input, Alert } from 'antd';
import ResizablePanel from 'component/resizable-panel';
import { hideFilter, updateFilter } from 'action/globalStatusAction';
import { MenuKeyMap } from 'common/constant';
import Style from './record-filter.less';
import CommonStyle from '../style/common.less';
class RecordFilter extends React.Component {
constructor () {
super();
this.onChange = this.onChange.bind(this);
this.onClose = this.onClose.bind(this);
this.filterTimeoutId = null;
}
static propTypes = {
dispatch: PropTypes.func,
globalStatus: PropTypes.object
}
onChange (event) {
this.props.dispatch(updateFilter(event.target.value));
}
根据文件头部的信息跟随前往action/globalStatusAction
能够找到updateFilter
方法定义。
export function updateFilter(filterStr) {
return {
type: UPDATE_FILTER,
data: filterStr
};
}
继续梳理逻辑,在record-worker
部分的代码中,找到生成过滤正则的方法getFilterReg
。
const getFilterReg = function (filterStr) {
let filterReg = null;
if (filterStr) {
let regFilterStr = filterStr
.replace(/rn/g, 'n')
.replace(/nn/g, 'n');
// remove the last /n$/ in case an accidential br
regFilterStr = regFilterStr.replace(/n*$/, '');
if (regFilterStr[0] === '/' && regFilterStr[regFilterStr.length - 1] === '/') {
regFilterStr = regFilterStr.substring(1, regFilterStr.length - 2);
}
regFilterStr = regFilterStr.replace(/((.+)n|(.+)$)/g, (matchStr, $1, $2) => {
// if there is 'n' in the string
if ($2) {
return `(${$2})|`;
} else {
return `(${$1})`;
}
});
try {
filterReg = new RegExp(regFilterStr);
} catch (e) {
console.error(e);
}
}
return filterReg;
};
接下来思路就很清晰了,首先需要修改生成过滤正则的getFilterReg
方法,使其忽略大小写,然后再定位到引用该方法的地方,加入对Body
数据的过滤代码,这样就符合我们的功能需求了。
先处理大小写敏感的问题,忽略大小写只需要在正则最后加入i
选项即可,代码做如下修改。
const getFilterReg = function (filterStr) {
let filterReg = null;
if (filterStr) {
let regFilterStr = filterStr
.replace(/rn/g, 'n')
.replace(/nn/g, 'n');
// remove the last /n$/ in case an accidential br
regFilterStr = regFilterStr.replace(/n*$/, '');
if (regFilterStr[0] === '/' && regFilterStr[regFilterStr.length - 1] === '/') {
regFilterStr = regFilterStr.substring(1, regFilterStr.length - 2);
}
regFilterStr = regFilterStr.replace(/((.+)n|(.+)$)/g, (matchStr, $1, $2) => {
// if there is 'n' in the string
if ($2) {
return `(${$2})|`;
} else {
return `(${$1})`;
}
});
try {
filterReg = new RegExp(regFilterStr,"/i");
} catch (e) {
console.error(e);
}
}
return filterReg;
};
在修改之前搜索只有大小写正确才可以过滤出数据,就像这样。
修改完成后,过滤功能下不再对大小写敏感。
这样的话过滤功能对大小写敏感的问题就算是解决了,接下来还剩一个对Body
数据进行过滤的需求,寻找getFilterReg
方法的引用,顺藤摸瓜,可以找到最终的过滤实现方法calculateFilteredRecords
。
self.calculateFilteredRecords = function (isFullyCalculate, listForThisTime = []) {
const filterReg = getFilterReg(self.filterStr);
if (isFullyCalculate) {
self.FILTERED_RECORD_LIST = [];
const length = recordList.length;
// filtered out the records
for (let i = 0; i < length; i++) {
const item = recordList[i];
if (!filterReg || (filterReg && filterReg.test(item.url))) {
self.FILTERED_RECORD_LIST.push(item);
}
}
} else {
listForThisTime.forEach((item) => {
const index = self.FILTERED_RECORD_LIST.findIndex((record) => {
return item.id === record.id;
});
if (index >= 0) {
self.FILTERED_RECORD_LIST[index] = item;
} else if (!filterReg || (filterReg && filterReg.test(item.url))) {
self.FILTERED_RECORD_LIST.push(item);
}
});
}
};
简单阅读下代码,通过filterReg.test(item.url)
这部分代码,很容易发现先前生成的正则表达式只对URL
进行了匹配,成功匹配后才会将其显示到页面中,在if
中将匹配Body
的代码用或运算进行连接,就可以实现我们的需求了。
接下来需要先找到Body
数据在哪才能进行匹配,根据item.url
也不难猜到Body
数据肯定也在item
中,此时可以选择浏览器控制台直接查看。
也可以翻阅代码进行查找,根据点击请求时右侧会展示请求的详情为切入点,定位到record-request-detail
代码中的getReqBodyDiv
方法。
getReqBodyDiv() {
const { recordDetail } = this.props;
const requestBody = recordDetail.reqBody;
const reqDownload = "<a href={"/fetchReqBody?id=${recordDetail.id}&_t=${Date.now()}"} target="_blank">download</a>";
const getReqBodyContent = () => {
const bodyLength = requestBody.length;
if (bodyLength > MAXIMUM_REQ_BODY_LENGTH) {
return reqDownload;
} else {
return "<div>{requestBody}</div>"
}
}
// ....
根据代码中的recordDetail.reqBody
可以看出Body
内容对应的是.reqBody
,于是对代码做如下修改。
self.calculateFilteredRecords = function (isFullyCalculate, listForThisTime = []) {
const filterReg = getFilterReg(self.filterStr);
if (isFullyCalculate) {
self.FILTERED_RECORD_LIST = [];
const length = recordList.length;
// filtered out the records
for (let i = 0; i < length; i++) {
const item = recordList[i];
if (!filterReg || (filterReg && (filterReg.test(item.url) || filterReg.test(item.reqBody)))) {
self.FILTERED_RECORD_LIST.push(item);
}
}
} else {
listForThisTime.forEach((item) => {
const index = self.FILTERED_RECORD_LIST.findIndex((record) => {
return item.id === record.id;
});
if (index >= 0) {
self.FILTERED_RECORD_LIST[index] = item;
} else if (!filterReg || (filterReg && (filterReg.test(item.url) || filterReg.test(item.reqBody)))) {
self.FILTERED_RECORD_LIST.push(item);
}
});
}
};
对比一下修改前后的效果,修改后已达预期效果,手动检测只需要打开网页就可以快速进行,不用运行BurpSuite
之类的客户端代理软件,体验优化+1
。
源码修改已完成,正常来说需要重新部署一下才可以使用,这里提供命令方式对已安装的AnyProxy
程序代码打个补丁,本文使用的版本为 4.1.3
,安装完主程序后运行如下命令,即可达到一样的效果。
# 需自行确认下文件路径是否一致
sed -i "s/f.test(d.url)/(f.test(d.url)||f.test(d.reqBody))/g" /usr/local/lib/node_modules/anyproxy/web/dist/*.js
sed -i 's/new RegExp(f)/new RegExp(f,"i")/g' /usr/local/lib/node_modules/anyproxy/web/dist/main.js
至此还有一个问题,由于目前是命令行启动的AnyProxy
,会话一旦断开,AnyProxy
就会一起结束运行,为了随时可以使用,需要让AnyProxy
常驻后台运行,这里后台进程管理交给PM2
来处理。
PM2
的安装非常简单,使用NPM
即可快速安装。
npm install pm2 -g
安装完成后使用PM2
启动AnyProxy
。
pm2 start anyproxy --name anyproxy -- -i -p 1080 -w 8002
命令执行后会列出状态信息,如下所示。
此时SSH
会话或者终端就可以放心关闭了,需要使用时直接访问网页就可以了。
会用到几条命令做个记录。
# 查看状况
pm2 list
# 关闭 anyproxy
pm2 stop anyproxy
# 启动 anyproxy
pm2 start anyproxy
# 重启 anyproxy
pm2 restart anyproxy
2.2 规则文件编写
通过上述修改已经可以方便的进行内容筛选,如果想实现自动对内容进行匹配,就需要编写自定义的规则,对预定义的敏感字进行匹配检测,参照官方文档编写规则,要使用的关键接口为beforeSendRequest
,以下为该接口的定义。
在该接口中编写过滤代码,敏感字通过正则进行匹配,将规则文件保存为rule_app.js
,其中关键部分如下。
*beforeSendRequest(requestDetail) {
var reg = /mac|imei|device|os.*version|wifi|ssid|app.*list|android.*id|ip|gps|phone.*number|location;/gi;
var url = requestDetail.url;
var body = requestDetail.requestData.toString();
if(url.search(reg)>=0 || body.search(reg)>=0)
{
fs.appendFileSync(log, "URL:[" + url + "]nBody:[" + body + "]nn", 'utf-8', function (err) {
if (err) throw err;
console.log('err');
});
}
return null;
},
规则对内容进行正则匹配,如匹配到则将URL
、Body
信息输出至纯文本文件中,这里只用作演示,可以自行更改为存入数据库、输出表格等存储方式,脚本编写完成后上传至服务器中,首先将正在运行的AnyProxy
关闭,之后带上自定义规则文件的参数重新启动。
# 关闭 AnyProxy
pm2 stop anyproxy
# 带参数启动
anyproxy --intercept --port 1080 -w 8002 --rule rule_app.js
重新启动完成后,手机打开应用,等待应用启动完成后查看服务器中的纯文本文件是否有内容,有则说明匹配到了相关字段,文件路径可以在规则文件rule_app.js
中进行配置。
后续将编写一个上层调度程序,使用ADB
将应用安装至系统,启动AnyProxy
之后唤起应用程序,等待启动完成后对结果进行检测,如存在信息,再人工对数据进行二次核验,确定最终结论。
3、 结语
至此已初见成效,进行检测工作更加便捷,规则文件及修改后的程序已打包为附件,可在公众号中获取,需要的朋友可以点个关注,防止迷路!
本文涉及的文件可以在公众号回复 0721-APP
获取,如果本文对您有帮助,来个点赞、在看就是对我们莫大的鼓励。
推荐阅读
01-21 特稿
03-17 特稿
06-02 特稿
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
原文始发于微信公众号(宸极实验室):『杂项』移动安全测试环境搭建(4)— 自动化APP违规上报信息检测
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论