1.大纲
主要是从几个点来了解安卓APP通用的解决办法:
- wifi代理抓包
- vpn抓包
- 客户端ssl验证解决方案
- 服务器ssl验证解决方案
- 双向ssl验证
- 非http协议抓包
- 自有的ssl验证协议
1.wifi代理抓包
当我们准备抓一个app的时候,第一件事肯定是抓包。传统的抓包解决方案是电脑安装Fiddler/Charles,手机WIFI配置好代理。给手机流量代理到我们的抓包软件,这样我们就可以完成对app的抓包:
一般没有证书效验的app,或者抓包检测的app我们都可以通过这种办法进行抓包。但是这里我们并不推介这种抓包的办法。我个人的习惯一般是使用vpn抓包的方式。
因为网上很多对wifi配置代理抓包的文章,这里就不一一概述。主要就是wifi设置代理,app安装抓包软件的证书。
这里简单的说说我们通过抓包软件抓到的包的原理,方便后面理解为什么需要过ssl验证:
这里用猿人学一张图简单来说明,非常简单明了。就是客户端 -> 抓包软件 -> 服务端。
1.1 优点
wifi代理抓包的优点可能就是配置非常非常方便,导入抓包软件的证书后也可以抓https的包。
1.2 缺点
相对于vpn抓包方案,wifi代理抓包最容易被检测。
1.2.1 wifi代理检测
缺点就是很容易被检测是否使用了代理:
System.getProperty("http.proxyHost")
System.getProperty("http.proxyPort")
如何判断app进行了wifi代理检测?
配置好代理,打开抓包软件,可以抓到app的其它请求但是抓不到这个app的数据,app直接显示无网络。这时候可能就是wifi代理被检测,或者有ssl证书效验。
wifi代理检测用的app还是比较多,建议采用vpn抓包的方式规避。
1.2.2 强制代理
或者是某些APP直接强制使用代理:
OkHttpClient client = new OkHttpClient().newBuilder().proxy(Proxy.NO_PROXY).build();
和我们py的requests一样,安卓也可以设置代理。设置请求走默认代理而不走系统代理,防止被抓包。
如何判断app是进行了强制代理?
设置了wifi代理后,关闭抓包软件,app依旧可以正常请求网络。可能是app强制进行了代理转发,或者压根没走http协议。
强制代理的情况很少,目前没有遇见过,这时候可以通过tcpdump抓取网络报文可以发现App直接和服务器建立了连接而没有和代理建立连接
2 VPN抓包
vpn抓包个人习惯使用Postern+Charles的组合。这里非常推介,所以写一下安装和配置的过程
2.1 环境
简单的介绍一下我的环境:
- Postern-3.1.2
- charles-proxy-4.6.2-win64
- win 10
- 安卓8,面具root
注意电脑的防火墙要关闭,否则可能导致流量无法转发。之前出现过公司电脑不行,个人笔记本可以的情况。然后关闭公司电脑后charles才能连接到手机。害我折腾了半天
2.2 安装配置
先给我们的手机安装Postern,电脑安装charles。然后配置charles
2.2.1 Charles配置
打开charles设置proxy,点击proxy->proxy-settings,开启socks然后配置一个端口,我这里给的是8889,使用socks代理来转发流量。
保存后再点击proxy->ssl proxying后,配置抓https的包
规则,两个都填*就行了
2.2.2 charles导出证书
从charles导出证书,注意charles导出证书的时候会导出在你选择的路径上一级(有点坑)
推送到手机,并安装该证书
adb push xxx.pem /sdcard
安卓7以后不信用户目录的证书,所以需要把证书移至系统证书目录。利用面具的Move Certificates模块来移动证书。
重启自动迁移证书。
2.2.3 没有面具迁移证书的办法
有时候手机使用的su去进行的root,没有使用面具。所以这么好的插件就无法使用了,这时候我们可以用命令进行操作:
先安装charles证书,再获取root权限
su
mount -o rw,remount -t auto /system
执行后,通过 mount | grep /system 查看,很明显有了rw权限。还有就是mount的时候要加上-t auto,不然我手机提示设备繁忙。
再移动用户证书到系统证书:
mv /data/misc/user/0/cacerts-added/* /system/etc/security/cacerts/
执行完后考虑到系统安全,最好改回ro权限:
mount -o ro,remount -t auto /system
验证一下证书是否在系统目录:
2.2.4 Postern配置
charles配置好后,打开手机的Postern添加一个代理
名字随便取,地址是电脑的局域网ip,端口是之前设置的socket端口,然后协议选择socks5,保存。
配置代理规则,添加一个规则。匹配类型是匹配所有地址,动作是通过代理连接,代理组选择刚配置好的代理即可。
app设置vpn为charles的代理后,正常情况下会弹出这个提示:
这样就配置就可以通过vpn抓包的办法去抓包了。
2.3 优点
相较于wifi代理抓包的办法,vpn检测相对较少。而且一次配置终生受益。
2.4 缺点
虽然vpn抓包的方式非常棒,但是也并不是全能的,它也是可以被检测的。
public void isDeviceInVPN() {
try {
List 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.");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
可以通过判断网络接口名字包含 ppp0 或 tun0是否有vpn代理,当然也有很多其它的办法进行检测。但是对app进行vpn检测的还是很少,除非是安全性要求较高的app。
并且现在的许多app都开始增加了app抓包的防范,比如说ssl 单向/双向验证,socket协议,某些有实力的大厂自己实现一套协议。
3 客户端ssl验证
在抓https的包的时候我们经常涉及到一个证书的问题,再说这个之前简单的说说http和https:
http是明文传播,易被拦截和修改。
https在HTTP的基础上加了一安全层(SSL或TSL)
这也就引出了我们抓https包的时候,容易遇见的ssl证书问题。
https实际上就是http+ssl。由于http发送的数据直接就是明文。安全性非常差。https会在数据发送前,先用ssl进行加密
客户端ssl验证简单来说就是客户端验证服务端返回的证书,从刚才第一点说的wifi代理抓包的原来来说,如果我们使用了中间人软件来抓包,其实真正请求服务端和响应客户端的是我们抓包软件。这时候客户端验证的是抓包软件的证书,所以肯定验证会失败!
刚好这里有hatch的一个案例,我们通过这个案例来学习如何bypass 客户端的ssl验证。
3.1 案例
该app是波兰某电商app,刚好app客户端验证了服务端的证书。
3.1.1 vpn抓包
先看看我们vpn抓包的表现效果,打开我们的charles和postern。然后打开app进行抓包:
app显示:
虽然看不懂波兰语,但是大概的意思应该是没有网络连接了
charles显示:
抓包都是连接失败,请求里面也提示了证书问题:SSL handshake with client failed: An unknown issue occurred processing the certificate (certificate_unknown)
3.1.2 bypass ssl pinning
一般这种情况都是app验证了服务端的证书,服务端验证app证书的app非常少,因为太消耗服务器资源。除了soul之前是双向验证之外(后面新版本好像也取消了双向验证)
这里推介使用frida 的一个bypass 脚本,就是hook掉验证的代码即可:
https://github.com/WooyunDota/DroidSSLUnpinning/blob/master/ObjectionUnpinningPlus/hooks.js
下载这段通用的hook脚本,先启动我们的frida server:
ail@aildeMacBook-Pro ~ % adb shell
hammerhead:/ $ su
hammerhead:/ # cd /data/local/tmp/
hammerhead:/data/local/tmp # ./frs14
使用spwan的方式hook:
ail@aildeMacBook-Pro ObjectionUnpinningPlus % workon firda14
ERROR: Environment 'firda14' does not exist. Create it with 'mkvirtualenv firda14'.
ail@aildeMacBook-Pro ObjectionUnpinningPlus % workon frida14
(frida14) ail@aildeMacBook-Pro ObjectionUnpinningPlus % frida -U -f pl.allegro -l hooks.js --no-pause
____
/ _ | Frida 14.2.18 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
Spawned `pl.allegro`. Resuming main thread!
[Nexus 5::pl.allegro]-> message: {'type': 'send', 'payload': 'Custom, Empty TrustManager ready'} data: None
message: {'type': 'send', 'payload': 'com.squareup.okhttp not found'} data: None
message: {'type': 'send', 'payload': 'registerClass from hostnameVerifier >>>>>>>> Missing implementation for: boolean verify(java.lang.String, javax.net.ssl.SSLSession)'} data: None
message: {'type': 'send', 'payload': 'Xutils hooks not Found'} data: None
message: {'type': 'send', 'payload': 'httpclientandroidlib Hooks not found'} data: None
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl pinning'} data: None
message: {'type': 'send', 'payload': 'Trustkit pinner not found'} data: None
[-] Cronet pinner not found
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl.verifyCertificateChain'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'Overriding SSLContext.init() with the custom TrustManager'} data: None
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl.verifyCertificateChain'} data: None
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl.verifyCertificateChain'} data: None
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl.verifyCertificateChain'} data: None
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl.verifyCertificateChain'} data: None
message: {'type': 'send', 'payload': 'OpenSSLSocketImpl.verifyCertificateChain'} data: None
抓包成功:
当然解决方案不止这一种,我们还可以hook掉加载证书的地方,给证书替换成我们抓包软件的证书就行
4 服务端ssl验证
服务端ssl验证就是服务端验证ssl证书,这时候就需要我们找到app的证书和密码。然后将app中内置的证书导入到Charles中去。
一般通用的找证书的办法就是反编译apk后,直接通过grep搜索p12后缀的文件名。但是一些app会将证书改成jpg或者gif等其它后缀名。这时候我们可以hook app加载证书的地方,输出证书路径和密码。
这时候我们可以使用肉丝巨巨的r0capture,github地址是:
https://github.com/r0ysue/r0capture
r0capture不止提供了证书导出的功能,还提供了非常强大的功能,这里因为我用的比较少后面有机会进行补充。
找到证书后,再charles的Proxy→SSL Proxy Settings→Client Certificates→Add添加新的证书即可。
5 双向验证
双向验证的app太少了,这里没有案例。参考单向的客户端验证和服务端验证的解决方案。只是需要同时解决客户端验证和服务端验证证书的问题。
6 非http协议抓包
现在一些app不走http协议,而选择一些开源的非http协议。比如某手的quic协议和某宝的Spdy协议,默认并不采用传统的http或者https协议。
这种情况下我们如何进行抓包呢?
6.1 某宝抓包
我们以某宝为例,淘宝采用的是Spdy协议,如果要直接去抓这个协议的数据估计要去研究该协议了或者直接hook app构造参数的地方,输出参数内容,避免直接抓包需要研究一个协议带来的时间成本。
但是索性的是不论是某宝还是某手,某团都提供了一个降级成http协议方案,当他们协议使用的服务器无法连接时会采用http协议去连接其他服务器。所以这里就提出来了两个解决方案:
- iptables 直接干掉所有tcp或者udp这种可疑的协议,使他们被迫降级成http协议
- hook掉开关,直接让app采用http协议
这里只说hook的方案:
这里就是它的开关,我们hook后直接返回false就行:
Java.perform(
function(){
var SwitchConfig_2 = Java.use('mtopsdk.mtop.global.e')
SwitchConfig_2.d.implementation = function(){
return false
}
SwitchConfig_2.e.implementation = function(){
return false
}
}
)
hook后app就会采用http协议了
7 自集成的SSL库
除了使用tcp协议外,现在还有一些有实力的大厂APP基于开源的SSL库,自己集成了一套SSL库。这样我们传统的单向/双向证书解决的方案又行不通了。因为它是自己实现的一套SSL库,和安卓默认的SSL效验的地方肯定不一样。
这里我们就以最新版本的抖音为例。
7.1 最新版的抖音
定位过程参考:https://bbs.pediy.com/thread-269028.htm