走进移动安全(3)-抓包进阶
应用层抓包对抗
人与人的相遇,不是恩赐就是劫。
上篇我们简单介绍了抓包,但是实际的操作中经常会出现抓不到包,令人非常的懊恼,本篇我们来看下app抓包过程中遇见的对抗手段。大致可分为以下:
1. SSL证书绑定(单向校验和双向校验)
2. 代理检测、VPN检测、发包框架强制不走代理
3. 自定义Soket、ssl库等
SSL证书绑定
什么是证书绑定呢?其实网上叫法蛮多的,SSL证书绑定、英文名字:SSL Pinning或者证书检验。总之无论怎么叫都是检验证书是否可信任。我们知道从HTTP到HTTPS数据在传输过程中添加了一层SSL/TLS,让我们数据流量处于加密状态,不再是明文可见。这时候便有了CA证书。
我们在抓取https数据包得时候,做的就是利用假的CA证书,来实现中间人劫持数据。一旦app校验了证书的指纹信息。我们的证书不再受信任了。自然而然就无法建立连接,所以必须想办法让app信任,才能继续抓包。当然这个分为两种情况:
1. 单项校验-客户端校验服务端的证书。
2. 双向认证-客户端不仅仅要校验服务端的证书,也会在app内放一张证书;服务端也会检验客户端里的证书。这里要提一下Android系统默认对证书信任证书的问题。
安卓7.0之前系统 直接下载证书装入即可,安卓7.0及以上系统对于证书的安全策略做了修改,意味着,从sdcard安装用户级CA将无法拦截应用流量。我们需要将证书命名为计算出的哈希值后缀.0导入到根证书目录:/system/etc/security/cacerts 让系统默认可信任。导入证书具体步骤可参考上篇文章:
https://mp.weixin.qq.com/s/m-VjaS7nEzmGIVV4aIxZ9Q
需要注意的是:在Android 10以及以上安装证书到/system/etc/security/cacerts/会出现system无法写入使用mount报错如下:
'/system' not in /proc/mounts
'/dev/block/dm-4' is read-only
遇见此类情况有两种方式解决:
1. magisk插件帮助我们导入证书:
https://github.com/Magisk-Modules-Repo/movecert2. 创建一个新的挂载点来覆盖 这种方式是内存覆盖的方式所以手机重启后失效。(想要持久化需考虑搞一个开机启动服务)
# 创建一个临时目录,保存当前证书
mkdir /sdcard/tmp/
# 复制现有证书到临时目录
cp /system/etc/security/cacerts/* /sdcard/tmp/
# 创建内存挂载
mount -t tmpfs tmpfs /system/etc/security/cacerts
# 将现有证书复制回 tmpfs 挂载
mv /sdcard/tmp/* /system/etc/security/cacerts/
# 更新 perms 和 selinux
chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*
我们继续说证书校验问题。
单向校验
Android 系统中已经提供了检验证书的api,我们只需要实现checkClientTrusted、checkServerTrusted、verify等方法即可。
这类的对抗需要我们将这些函数的校验进行置空,默认信任所有证书即可。
典型的xposed 插件SSLUnping
https://github.com/cxf-boluo/magisk_All/blob/main/apks/mobi.acpm.sslunpinning.apk
objection 中使用
android sslpinning disable
都可以帮助我们置空这些校验的函数。
双向认证
APP 除了校验服务端的证书,服务端还会检验 APP 的证书。https 双向证书校验在实际中几乎很少用到,因为服务器端需要维护所有客户端的证书,这无疑增加了很多消耗,因此大部分厂商选择使用单向证书绑定。 对抗双向认证需要完成两个环节:
(1) 让客户端认为 burp 是服务端 这一步其实就是破解 ssl Pinning,方法和上述过程完全相同。
(2) 让服务端认为 burp 是客户端 这一步需要导入客户端的证书到 burp,客户端的证书一定会存在本地代码中,而且还可能会有密码,这种情况下需要逆向客户端 app,找到证书和密码,并转为 pkcs12 格式导入到 burp。User options -> SSL -> Client SSL Certificate。
通常情况下应用会将证书放置在资源目录app/asset下,后缀名为p12、pfx的文件。当然也可能会伪装成其他文件,例如图片文件等。
怎么找到证书密码呢?一般要么逆向分析找到密码,要么通过hook api java.security.KeyStore 使密码自吐。
1.jadx中搜索证书的名字、或者证书链x509certificate分析定位到关键位置。
2.服务器对客户端进行校验过程中,客户端将证书公钥发送给服务器,以及从服务器获取session和私钥解密过程中,需要API进行操作,API存在于java层框架内,所以hook框架层代码java.security.KeyStore,使密码自吐。
function hook_KeyStore_load() {
Java.perform(function () {
var StringClass = Java.use("java.lang.String");
var KeyStore = Java.use("java.security.KeyStore");
KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {
console.log("KeyStore.load1:", arg0);
this.load(arg0);
};
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
printStack("KeyStore.load2");
console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
this.load(arg0, arg1);
};
console.log("hook_KeyStore_load...");
});
}
Burp 导入客户端中的证书教程:
https://bbs.pediy.com/thread-265404.htm
webview证书校验
当app不再采用原生开发,而是使用h5利用webview去加载页面就可能会采用webview证书校验。或者是混合开发,部分原生,内嵌h5 抓到包的时候就会某些功能可以正常抓到,某个页面白屏或者无法访问。Android WebView组件加载网页发生证书认证错误时,会调用WebViewClient类的onReceivedSslError方法。
mWebview.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
if("trustAllCerts".equals(tag)){
handler.proceed(); //忽略证书
} else {
handler.cancel(); //默认白屏
}
}
});
我们可以先使用插件SSLUnping 或者objection 中的 android sslpinning disable先附加。因为插件可能并没有覆盖某个点,我们也可以自己去hook系统的api。
Java.perform(function() {
try {
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
console.log('[+] Bypassing TrustManagerImpl (Android > 7): ' + host);
return untrustedChain;
};
} catch (err) {
console.log('[-] TrustManagerImpl (Android > 7) pinner not found');
//console.log(err);
}
});
未知证书绑定怎么处理
1.当app使用了未知的证书框架,并且混淆了。我们使用的这些插件不能生效了。这时候就需要我们去定位、找到证书绑定的位置去做出相应的处理。既然是证书绑定一般都会进行对证书文件读取的操作所以我们可以hook 系统api java.io.File.$init 这个函数并打印调用堆栈的方式帮助我们分析定位证书绑定的地方。当然这个不能以偏概全,毕竟也有把证书公钥信息写在代码中来进行对比,不进行文件操作。
2.也可以去确认是否使用主流框架okhttp、HttpURLconnection等 利用objection 去搜索内存并批量hook
.objection # android hooking search classes volley
.objection # android hooking search classes okhttp
.objection # android hooking search classes HttpURLconnection
3.使用查看调用轨迹的工具ZenTracer,来辅助我们定位经过的方法。https://github.com/hluwa/ZenTracer
代理检测
既然是抓包一般情况下我们都要在手机中去设置代理到我们的电脑。设置代理的两种方式
第一种方法:
第二个方法设置代理:
adb shell settings put global http_proxy 192.168.xx.xxx:8888 获取当前系统是否设置代理,可以根据不同的 Api Level,通过 System.getProperty() 和 android.net.proxy.getXxx() 方法进行检测。
private fun checkWifiProxy(): Boolean {
val IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH
val proxyAddress: String?
val proxyPort: Int?
if (IS_ICS_OR_LATER) {
proxyAddress = System.getProperty("http.proxyHost")
val portStr = System.getProperty("http.proxyPort")
proxyPort = Integer.parseInt(portStr ?: "-1")
} else {
proxyAddress = android.net.Proxy.getHost(this)
proxyPort = android.net.Proxy.getPort(this)
}
Log.i("cxmyDev","proxyAddress : ${proxyAddress}, prot : ${proxyPort}")
return !TextUtils.isEmpty(proxyAddress) && proxyPort != -1
}
利用框架强制不走代理
对于一些常用的网络库,其实是提供了我们设置的代理的接口,我们只需要将其设置成无代理的模式,它就不会走应用系统默认的代理进行网络请求。即使我们设置了代理也是会被忽略。最直观的表现就是,明明设置了代理,抓包工具中没有app的流量但是app与服务器可以正常交互运行。例如比较常用的 OkHttp就可以直接设置 NO_PROXY
VPN检测
当我们抓不到包时,有时候会采用VPN抓包,VPN 抓包的本质是在网络层/路由层抓包,例如 小黄鸟:
https://github.com/cxf-boluo/magisk_All/blob/main/apks/com.guoshi.httpcanary.apk
常见的检测方式是 tun0 PPP0 Transpoart 等特征:
检测tun0 PPP0
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (name.equals("tun0") || name.equals("ppp0")) {
Log.i("TAG", "isDeviceInVPN current device is in VPN.");
}
检测 Transpoart
ConnectivityManager connectivityManager =(ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); Network network = connectivityManager.getActiveNetwork();
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
Log.i("TAG", "networkCapabilities -> " + networkCapabilities.toString());
如何应对 代理检测、强制不走代理、VPN检测
1. hook相关函数
2. 反编译修改代码逻辑
3. 可以使用iptables对请求进行强制转发,ProxyDroid全局代理工具底层原理通过iptables实现的,所以使用ProxyDroid开启代理,可以比较有效的绕过代理检测。iptables路由重定向
• 设置重定向:iptables -t nat -A OUTPUT -d 0.0.0.0/0 -p tcp -j DNAT --to <电脑IP地址>
• 取消重定向:iptables -t nat -D OUTPUT -d 0.0.0.0/0 -p tcp -j DNAT --to <电脑IP地址>
ProxyDroid下载地址:
https://github.com/cxf- boluo/magisk_All/blob/main/apks/org.proxydroid.apk
4.okhttp3中因设置NO_PROXY,不走系统代理情况的frida hook脚本。
Java.perform(function() {
//okhttp3 过 proxy(Proxy.NO_PROXY)
var okHttp = Java.use("okhttp3.OkHttpClient$Builder");
var jproxy = Java.use("java.net.Proxy");
var type = Java.use("java.net.Proxy$Type");
var isa = Java.use("java.net.InetSocketAddress");
okHttp.proxy.overload("java.net.Proxy").implementation = function() {
var sa = isa.$new("192.168.0.81", 9999); //此处为 自己代理的IP和端口号
var myproxy = jproxy.$new(type.HTTP.value, sa);
arguments[0] = myproxy;
var ret = this.proxy.apply(this, arguments);
return ret;
}
})
5.同理我们知道VPN检测也是通过api获取判断,所以我们对api进行hook隐藏。
function main() {
Java.perform(function () {
Java.use("java.net.NetworkInterface").getName.implementation = function(){
var string_class = Java.use("java.lang.String");
var gname = this.getName();
if(gname == string_class.$new("tun0")){
console.log("find ===> ", gname);
return string_class.$new("rmnet_data0")
} else{
console.log("gname ===> ", gname)
}
return gname;
}
// Java.use("android.net.ConnectivityManager").getNetworkCapabilities.implementation = function(v){
// console.log(v)
// var res = this.getNetworkCapabilities(v)
// console.log("res ==> ", res)
// return null;
// }
Java.use("android.net.NetworkCapabilities").hasTransport.implementation = function(v){
console.log(v)
var res = this.hasTransport(v)
console.log("res ==> ", res)
return false;
}
})
}
setImmediate(main);
hook抓包
我们知道HTTP协议是没有加密的直接hook dump下来即可(参考r0capyure)
而https协议我们也是从系统层下手比如去hook: /system/lib/libssl.so库中的 SSL_read() 与SSL_write()函数。例如 frida_ssl_logger、r0capture:https://github.com/BigFaceCat2017/frida_ssl_logger https://github.com/r0ysue/r0capture
攻防永无止境,部分开发实力过强的大厂或框架,采用的是自身的SSL框架,这时候我们再去hook ssl库中的函数自然而然就无效了。也有一些app采用内置Vray、Shadowsocks 也是可以一定程度上对抗抓包。还是需要具备比较强的逆向分析、以及对系统的理解。
随手分享、点赞、在看是对我们最大的支持