服务端校验客户端
案例:爱奇艺
平时我们碰到的HTTP和HTTPS都在应用层,SOCKS在会话层,TCP和UDP在传输层,IP在网络层。
HTTP未加密主要有这些不足
- 通信使用明文(不加密),内容可能会被窃听
- 不验证通信方的身份,因此有可能遭遇伪装 DNS劫持->GFW翻墙
- 无法证明报文的完整性,所以有可能已遭篡改 运营商劫持->弹窗广告
HTTP+加密+认证+完整性保护=HTTPS,是身披SSL的HTTP。
通信加密:HTTP协议中没有加密机制,但可以通过和SSL( Secure Socket Layer,安全套接层)或TLS( Transport Layer Security,安全层传输协议)的组合使用,加密HTTP的通信内容。用SSL建立安全通信线路之后,就可以在这条线路上进行HTTP通信了。与SSL组合使用的HTTP被称为Https(HTTP Secure,超文本传输安全协议)
内容加密:客户端需要对HTTP报文内容进行加密处理后再发送请求。为了做到有效的内容加密,前提是要求客户端和服务器同时具备加密和解密机制。有一点必须引起注意,由于该方式不同于SSL或TLS将整个通信线路加密处理,所以内容仍有被篡改的风险。
如下图:
- 客户端发起https请求将SSL协议给服务端
- 服务端从CA机构申请一份CA证书(包括服务端公钥和签名)给客户端
- 客户端读取CA证书明文信息,采用同样hash函数计算拿到信息摘要,用系统自带的CA证书的公钥解密签名(签名由CA的私钥加密),对比证书中的信息摘要,一致则可信,再取出服务端的公钥去加密客户端生成随机数作为秘钥生成密文发给服务端
- 服务端用自己私钥解密拿到秘钥随机数
- 以上是非对称加密过程,后续服务端和客户端通讯过程就是通过该秘钥进行通讯,整个过程都是对称加密了。
对称加密 : 加密和解密数据使用同一个密钥。这种加密方式的特点是速度很快,常见对称加密的算法有 AES;
非对称加密: 加密和解密使用不同的密钥,这两个密钥形成有且仅有唯一的配对,叫公钥和私钥。数据用公钥加密后必须用私钥解密,数据用私钥加密后必须用公钥解密。一般来说私钥自己保留好,把公钥公开给别人(一般公钥不会单独出现,而是会写进证书中),让别人拿自己的公钥加密数据后发给自己,这样只有自己才能解密。 这种加密方式的特点是速度慢,CPU 开销大,常见非对称加密算法有 RSA。
一般情况下使用HTTP都是能抓到包的, 但是使用HTTPS会抓不到包?现在的HTTPS都是基于TLS协议的, 它的特点就是需要确认传输双方的身份。确认了身份之后再传输数据, 以上就是HTTPS避免中间人攻击的手段。
Charles,Fiddler,BurpSuite代理都是通过给wifi设置http代理的方式进行抓包,HTTPS是包裹在SSL协议里的HTTP,APP-Charles客户端校验服务端,Charles-服务器是服务端校验客户端,不使用charles证书校验失败所有https请求报错400 Bad Request,No required SSL certificate was sent
。 使用了服务端的证书访问服务端校验客户端的请求原理就是中间人攻击,而中间人攻击的关键就是截获服务器返回的证书并伪造证书发送给客户端骗取信任,所以在应用层拦截抓包,所以会被很轻易的检测到和绕过的。
1 | public static boolean isWifiProxy(Context context) { |
这两个API来查看当前系统是否挂了http代理,或者发起请求时OkHttpClient okHttpClient = new OkHttpClient.Builder(). proxy(Proxy.NO_PROXY). build();
直接设置禁止代理,会很轻松的让你的抓包失效。
趣充:设置no proxy防抓包,校验到直接强退APP,我们通过hook将这个函数替换掉即可。
1 | function replaceKill(){ |
所以我们需要换一种方式来设置代理postern,本质在tcp的传输层抓包。就是设置vpn代理,vpn是属于网络层的,设置了vpn后,你的手机上ifconfig后会多一个接口,等于加了一个虚拟网卡,所有的流量都会从这走。应用层和传输层的请求都可以拿到,还不会被上面提及的两个api所检测。
ip route show table 0|grep default
手机路由表第一条降维打击网络层,任何api都必须经过路由解析http。
不过App依旧可以通过判断java.net.NetworkInterface.getName()
是否等于tun0
或ppp0
或者 android.net.ConnectivityManager.getNetworkCapabilities
来判断是否存在VPN抓包,可以通过grep -ril "vpn"
反编译后的dex中找到所有关于vpn的函数。
通过objection hook以上的类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 function hook_vpn() {
Java.perform(function () {
var String = Java.use("java.lang.String");
var NetworkInterface = Java.use("java.net.NetworkInterface");
NetworkInterface.getName.implementation = funciont() {
var name = this.getName();
console.log("name: " + name);
if (name == "tun0") {
var result = String.$new("rmnet_data0")
console.log("hook result:" + result)
return result;
} else {
return name;
}
}
var ConnectivityManager = Java.use("android.net.ConnectivityManager")
ConnectivityManager.getNetworkCapabilities.implementation = function (args) {
var result = this.getNetworkCapabilities(args);
console.log("vpn result: " + result)
return null
}
})
}
setImmediate(hook_vpn);将检测抓包的函数给hook掉不就拦不住我抓包的步伐了嘛!
不过回过头来,为什么客户端这么容易被欺骗呢?原因就是过于依赖证书的校验合法性!市面上大部分的应用在校验证书是否过期,服务器证书域名与服务器实际域名是否匹配,证书链的完整校验都做的不够好,即时在完整校验了整个证书链体系时,中间人攻击依旧可以在终端手动添加信任根证书的方式发送请求,这也正是抓包软件在抓取HTTPS协议数据前,要求在终端安装证书的原因,确保证书通过客户端的证书链校验。以下可以将证书放到根目录,获取最高权限。
安卓8
1
2
3
4
5
6
7 cd /data/misc/user/0/cacerts-added/
mount -o remount,rw /
mount -o remount,rw /system
chmod 777 *
cp * /etc/security/cacerts/
mount -o remount,ro /
mount -o remount,ro /system安卓7
1
2
3
4
5
6
7 cd /data/misc/user/0/cacerts-added/
mount -o rw,remount /system
mount -o rw,remount /
chmod 777 *
cp * /etc/security/cacerts/
mount -o ro,remount /system
mount -o ro,remount /安卓10
Move_Certificates-v1.9 直接面具插件安排,重启即可
客户端校验服务端
那么FaceBook/Twitter这些大厂是如何做到防止Charles/Fiddler等抓包工具中间人攻击的呢?SSL-Pinning!原理是HTTPS建立时与服务端返回的证书比对一致性,进而识别出中间人攻击后直接在客户端侧中止连接。
证书锁定:开发时就将服务端证书一块打包到客户端里,不接受操作系统或浏览器内置的CA根证书对应的任何证书,通过这种授权方式,保障了APP与服务端通信的唯一性和安全性。但是CA签发证书都存在有效期问题,所以缺点是在证书续期后需要将证书重新内置到APP中。
公钥锁定:提取证书中的公钥并内置到客户端中,通过与服务器对比公钥值来验证连接的正确性。制作证书密钥时,公钥在证书的续期前后都可以保持不变(即密钥对不变),所以可以避免证书有效期问题,一般推荐这种做法。
不过由于客户端会做两个证书间的一次性校验,那么就通过hook的方式将此次校验的结果返回true或者干脆不让其做校验,或者Xposed安装JustTrustMe插件,该插件将各种已知的HTTP请求库中用于校验证书的API进行hook并不论是否可信证书都返回正常状态。
1 | objection -g com.ophone.reader.ui explore 咪咕阅读 |
git clone https://github.com/WooyunDota/DroidSSLUnpinning.git
frida -U -f com.ninemax.ncsearchnew -l ObjectionUnpinningPlus/hooks.js –no-pause
1 | Java.perform(function() { |
双向校验
SSL pinning实际上是客户端锁定服务器端的证书, 在要与服务器进行交互的时候, 服务器端会将CA证书发送给客户端, 客户端会调用函数对服务器端的证书进行校验, 与本地的服务器端证书(存放在\\asset
目录或\res\raw
下)进行比对。
而双向校验是添加了客户端向服务器发送CA证书, 服务器端对客户端的证书进行校验的部分。
客户端和服务端同时检验数据的加密和解密
- 客户端发起HTTPS请求,将SSL协议信息发送给服务端。
- 服务端去CA机构申请来一份CA证书,在前面提过,证书里面有服务端公钥和签名。将CA证书发送给客户端
- 客户端发送自己的客户端证书给服务端,证书里面有客户端的公钥,并发送支持的对称加密方案给服务端,供其选择
- 服务端选择完加密方案后,按照加密方案完成刚才得到的公钥去加密
- 客户端用自己的私钥去解密选好的加密方案,客户端生成一个随机数(密钥),用刚才等到的服务端公钥去加密这个随机数形成密文,发送给服务端。
- 服务端和客户端在后续通讯过程中就使用这个密钥进行通信了。和之前的非对称加密不同,这里开始就是一种对称加密的方式
Soul
Charles
soul在登录时报错400 No required SSL certificate was sent
,缺少证书,需要在charles中安装客户端证书获取服务器信任。
Frida
Socket本质:收发包的接口,一条跑着RAW DATA的通道,纯binary,为了方便使用TCP
或UDP
而抽象出来作为网络中连接的两端。应用领域SSL+HTTP,SMPT/POP/IMAP,Protobuf等。Socket是传输控制层接口,WebSocket是应用层协议用来创建一种双向通信(全双工)的协议 ,来弥补HTTP协议在持久通信能力
上的不足。
常用frida抓包,详情见某摩托的逆向分析
1 | git clone https://github.com/siyujie/OkHttpLogger-Frida.git |
Hook Socket
1 | function hook_socket(){ |
Hook KeyStore
1 | function printStack(str_tag) |
开启hook
1 | frida -U -f cn.soulapp.android -l ssl.js 通过attach进行hook公钥加解密的框架层api,打印密码用于解开证书用 |
或者通过反编译拿到证书加载的位置,关键词PKCS12
第二个红框中的load函数的第二个参数其实就是证书的密钥, 追根溯源, 我们可以知道v1参数是下图中调用的函数的返回值。
功能就是传递p0参数, 也就是说p0参数就是证书安装密码。想获取这个密码, 关键在于Auto_getValue函数。到这一步, 只要跟进Null_getStorePassword函数看看就好了。跟进去发现调用了native层的函数, 查看init函数中具体加载的是哪个so文件。
用IDA反编译soul-netsdk之后, 搜索字符串”getStorePassword”, 就定位到函数getStorePassword上了, F5之后, 获得伪代码和密钥
抓包
1 | 7z x soul_channel_soul.apk |
安装证书
导入charles - Proxy- SSL Proxying Settings 添加客户端证书
启动ssl抓包,Charles将对任意服务器发送该客户端证书,意味着用app访问所有的服务器,后续关闭客户端证书的soulapp.cn,否则会误认为请求来自于soul, 测试登录
fiddler中可以使用openssl将.p12
格式的证书转换成.cer/.der
格式的证书。(.der和.cer格式的证书仅有文件头和文件尾不同)
下面的命令实现了证书的格式转换, .p12
->.pem
->.cer
, 在生成.pem
格式的证书之后, 需要输入证书的密码, 也就是我们上面逆向获取的证书密码,也就是我们上面逆向获取的证书密码。最后将ClientCertificate.cer
移动到之前Fiddler弹窗出现的目录下, 也就是\Fiddler4
下。
1 | # 将.p12证书转换成.pem格式 |
趣充
frida -U -f com.whwy.equchong -l hook_keystore.js –no-pause -o quchong.txt 证书位置和密钥在KeyStore.load2后的/sdcard/Download
将p12证书拷贝出来,下载kse_5.4.4_all.deb,dpkg -i kse_2.4.4_all.deb,启动keystore explore打开p12证书,输入密码即可查看证书内容
右键-export-export key pair-输入密码 保存为p12,设置密码123456
charles-SSL Proxying Settings-Client Certificate-Import P12-导入导出的p12证书,密码为123456,Host Port配*表示所有发出去的包都用这个证书
启动Postern,配置charles抓包,重新注册获取验证码,依旧拿不到结果是,查看socket后的端口9443,在charles中配置9443端口,即可拿到完整加密请求结果
滴答
启动charles抓包,发送验证码,查看OverView
证书绑定校验失败:Client closed the connection before a request was made.Possibly the SSL certificate was rejected.You may need to configure your browser or application to trust the Charles Root Certificate.See SSL Proxyinf in the Help menu.
Android SSL证书设置和锁定(SSL/TLS Pinning)查看安卓设置-加密与凭据-信任的凭据
frida -U -f cn.ticktick.task -l quchong.js –no-pause 执行hook_ssl() 失败
objection -g cn.ticktick.task explore -s “android sslpinning disable” 失败
git clone https://github.com/WooyunDota/DroidSSLUnpinning.git
frida -U -f cn.ticktick.task -l hooks.js –no-pause 失败
1 | objection -g cn.ticktick.task explore -s "android hooking watch class_method java.io.File.$init --dump-args --dump-backtrace --dump-return" 打开证书文件就初始化这个文件,hook该类,发送验证码 |
1 | cd OkHttpLogger-Frida && frida -U -f cn.ticktick.task -l okhttp_poker.js |
https://square.github.io/okhttp/4.x/okhttp/okhttp3/-certificate-pinner/
1 | android hooking list class_methods z1.g 其中有z1.g.a,在CertificatePinner中被混淆的 |
多重证书绑定
咪咕视频
登录抓包,SSL handshake with client failed: An unknown issue occurred processing the certificate (certificate_unknown)
从抓包发现证书 绑定,可能客户端只信任信任的公钥签名,不信任就不允许,停止客户端访问的证书绑定。客户端发了,我们已经绕过了校验,把自己公钥发给charles,charles用自己私钥解开客户端的公钥发现不正常的结果。
1 | dumpsys activity top 查看包名 com.ophone.reader.ui |
证书绑定的逻辑没有hook掉:”at com.bangcle.andjni.JniLib.cL(Native Method)” → 只有逆代码来过证书绑定
1 | python r0capture.py -U com.ophone.reader.ui -v -p migu.pcap 关闭postern抓包,获取验证码没有更新log,说明没有一些底层的框架 |
通过wireshark查看migu.pcap结果,发现也没有关键性信息,抓包也抓不到。
1 | python r0capture.py -U -f com.ophone.reader.ui -v 尝试导出证书 |
SSL handshake with server failed - Remote host terminated the handshake
The remote SSL server rejected the connection. The server may require a specific certificate or cipher not supported by Charles.
过客户端证书后发现更多证书绑定,Frida.Android.Practice
1 | objection -g com.ophone.reader.ui explore -s "android sslpinning disable" |
git clone https://github.com/WooyunDota/DroidSSLUnpinning.git
1 | frida -U -f com.ophone.reader.ui -l hooks.js --no-pause 抓发送验证码包依旧有请求失败 |
FRIDA 使用经验交流分享,git clone https://github.com/deathmemory/FridaContainer.git
1 | cd utils/android |
北京银行
1 | python r0capture.py -U -f com.bankofbeijing.mobilebanking -v |
加固厂商自定义开发的证书绑定对抗很难被攻克。终极解决方案就是修改安卓底层aosp源码,将发送请求时打印log后,重编译成镜像刷机,比如项目crypto_filter_aosp,让整个系统都变成一个抓包工具。
参考: