抓包对抗

服务端校验客户端

案例:爱奇艺

网络七层模型

平时我们碰到的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将整个通信线路加密处理,所以内容仍有被篡改的风险。

如下图:

  1. 客户端发起https请求将SSL协议给服务端
  2. 服务端从CA机构申请一份CA证书(包括服务端公钥和签名)给客户端
  3. 客户端读取CA证书明文信息,采用同样hash函数计算拿到信息摘要,用系统自带的CA证书的公钥解密签名(签名由CA的私钥加密),对比证书中的信息摘要,一致则可信,再取出服务端的公钥去加密客户端生成随机数作为秘钥生成密文发给服务端
  4. 服务端用自己私钥解密拿到秘钥随机数
  5. 以上是非对称加密过程,后续服务端和客户端通讯过程就是通过该秘钥进行通讯,整个过程都是对称加密了。

公私钥加解密

对称加密 : 加密和解密数据使用同一个密钥。这种加密方式的特点是速度很快,常见对称加密的算法有 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static boolean isWifiProxy(Context context) {
final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
String proxyAddress;
int proxyPort;
if (IS_ICS_OR_LATER) {
proxyAddress = System.getProperty("http.proxyHost"); //获取代理主机
String portStr = System.getProperty("http.proxyPort"); //获取代理端口
proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
} else {
proxyAddress = android.net.Proxy.getHost(context);
proxyPort = android.net.Proxy.getPort(context);
}
Log.i("代理信息","proxyAddress :"+proxyAddress + "prot : " proxyPort")
return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}

这两个API来查看当前系统是否挂了http代理,或者发起请求时OkHttpClient okHttpClient = new OkHttpClient.Builder(). proxy(Proxy.NO_PROXY). build();直接设置禁止代理,会很轻松的让你的抓包失效。

趣充:设置no proxy防抓包,校验到直接强退APP,我们通过hook将这个函数替换掉即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function replaceKill(){
// 替换掉崩掉时的内容,打印崩掉时的参数和pid,不执行崩掉的逻辑,让程序不要崩
console.log("Preventing from killing ...")
var kill_addr = Module.findExportByName("libc.so", "kill");
// var kill = new NativeFunction(kill_addr,"int",['int','int']);
Interceptor.replace(kill_addr,new NativeCallback(function(arg0,arg1){
console.log("arg0=> ",arg0)
console.log("arg1=> ",arg1)

},"int",['int','int']))
}

function main(){
replaceKill()
}
setImmediate(main);

所以我们需要换一种方式来设置代理postern,本质在tcp的传输层抓包。就是设置vpn代理,vpn是属于网络层的,设置了vpn后,你的手机上ifconfig后会多一个接口,等于加了一个虚拟网卡,所有的流量都会从这走。应用层传输层的请求都可以拿到,还不会被上面提及的两个api所检测。

image-20220912105555810

ip route show table 0|grep default 手机路由表第一条降维打击网络层,任何api都必须经过路由解析http。

image-20221025221121870

不过App依旧可以通过判断java.net.NetworkInterface.getName()是否等于tun0ppp0 或者 android.net.ConnectivityManager.getNetworkCapabilities来判断是否存在VPN抓包,可以通过grep -ril "vpn"反编译后的dex中找到所有关于vpn的函数。

通过objection hook以上的类方法

image-20221025221752944

image-20221025222510180

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
2
3
objection -g com.ophone.reader.ui explore   咪咕阅读
android sslpinning disable 需要在启动时运行
objection -g com.ophone.reader.ui explore -s "android sslpinning disable" 登录页面触发解绑定,如果崩溃

git clone https://github.com/WooyunDota/DroidSSLUnpinning.git
frida -U -f com.ninemax.ncsearchnew -l ObjectionUnpinningPlus/hooks.js –no-pause

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
26
27
Java.perform(function() {
var array_list = Java.use("java.util.ArrayList");
var ApiClient = Java.use('com.android.org.conscrypt.TrustManagerImpl');

ApiClient.checkTrustedRecursive.implementation = function(a1, a2, a3, a4, a5, a6) {
// console.log('Bypassing SSL Pinning');
var k = array_list.$new();
return k;
}
}, 0);
//过证书绑定 ssl pinning 对证书在代码中进行额外校验,
function hook_ssl() {
// hook ssl 调用栈
Java.perform(function() {
var ClassName = "com.android.org.conscrypt.Platform";
var Platform = Java.use(ClassName);
var targetMethod = "checkServerTrusted";
var len = Platform[targetMethod].overloads.length;
console.log(len);
for(var i = 0; i<len; ++i) {
Platform[targetMethod].overloads[i].implementation = function () {
console.log("class:", ClassName, "target:", targetMethod, "i:", i, arguments);
printStack(ClassName + "." + targetMethod);
}
}
});
}

双向校验

SSL pinning实际上是客户端锁定服务器端的证书, 在要与服务器进行交互的时候, 服务器端会将CA证书发送给客户端, 客户端会调用函数对服务器端的证书进行校验, 与本地的服务器端证书(存放在\\asset目录或\res\raw下)进行比对。

而双向校验是添加了客户端向服务器发送CA证书, 服务器端对客户端的证书进行校验的部分。

客户端和服务端同时检验数据的加密和解密

  1. 客户端发起HTTPS请求,将SSL协议信息发送给服务端。
  2. 服务端去CA机构申请来一份CA证书,在前面提过,证书里面有服务端公钥和签名。将CA证书发送给客户端
  3. 客户端发送自己的客户端证书给服务端,证书里面有客户端的公钥,并发送支持的对称加密方案给服务端,供其选择
  4. 服务端选择完加密方案后,按照加密方案完成刚才得到的公钥去加密
  5. 客户端用自己的私钥去解密选好的加密方案,客户端生成一个随机数(密钥),用刚才等到的服务端公钥去加密这个随机数形成密文,发送给服务端。
  6. 服务端和客户端在后续通讯过程中就使用这个密钥进行通信了。和之前的非对称加密不同,这里开始就是一种对称加密的方式

Soul

Charles

soul在登录时报错400 No required SSL certificate was sent,缺少证书,需要在charles中安装客户端证书获取服务器信任。

image-20221026153714545

soul抓包报错

Frida

Socket本质:收发包的接口,一条跑着RAW DATA的通道,纯binary,为了方便使用TCPUDP而抽象出来作为网络中连接的两端。应用领域SSL+HTTP,SMPT/POP/IMAP,Protobuf等。Socket是传输控制层接口,WebSocket是应用层协议用来创建一种双向通信(全双工)的协议 ,来弥补HTTP协议在持久通信能力上的不足。

常用frida抓包,详情见某摩托的逆向分析

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
26
27
git clone https://github.com/siyujie/OkHttpLogger-Frida.git
adb push okhttpfind.dex /data/local/tmp
frida -U -l okhttp_poker.js -f com.motoband --no-pause
find() 查看是否使用okhttp并查看混淆结果,将Find Result下的内容复制到okhttp_poker.js开头,替换原有混淆类名
hold()
git clone https://github.com/BigFaceCat2017/frida_ssl_logger.git
解决IP的问题;
适配Windows;
andriod高版本适配;
iOS/macOS适配;
自实现ssl的适配;
适配socket的监控
新增对IP/dns的监控
新增对应用列表的显示
python ssl_logger.py -U -f com.qiyi.video >> iqiyi.txt
python ssl_logger.py -U -v com.iqiyi.video -p iqiyi.pcap
git clone https://github.com/r0ysue/r0capture.git
仅限安卓平台,测试安卓7、8、9、10、11、12 可用 ;
无视所有证书校验或绑定,不用考虑任何证书的事情;
通杀TCP/IP四层模型中的应用层中的全部协议;
通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
无视加固,不管是整体壳还是二代壳或VMP,不用考虑加固的事情;
frida -U -f com.motoband -l script.js --no-pause -o motoband.txt
python r0capture.py -U -f com.motoband -v
python r0capture.py -U -f com.motoband -v -p motoband.pcap
python r0capture.py -U -f com.motoband -v >>motoband.txt

Hook Socket

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
function hook_socket(){
Java.perform(function(){
console.log("hook_socket;")


Java.use("java.net.SocketOutputStream").write.overload('[B', 'int', 'int').implementation = function(bytearry,int1,int2){
var result = this.write(bytearry,int1,int2);
console.log("HTTP write result,bytearry,int1,int2=>",result,bytearry,int1,int2)
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log("bytearray contents=>", ByteString.of(bytearry).hex())
return result;
}


Java.use("java.net.SocketInputStream").read.overload('[B', 'int', 'int').implementation = function(bytearry,int1,int2){
var result = this.read(bytearry,int1,int2);
console.log("HTTP read result,bytearry,int1,int2=>",result,bytearry,int1,int2)
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log("bytearray contents=>", ByteString.of(bytearry).hex())
return result;
}

})
}


function hook_SSLsocketandroid8(){
Java.perform(function(){
console.log("hook_SSLsocket")

Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function(bytearry,int1,int2){
var result = this.write(bytearry,int1,int2);
console.log("HTTPS write result,bytearry,int1,int2=>",result,bytearry,int1,int2)
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log("bytearray contents=>", ByteString.of(bytearry).hex())
return result;
}



Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function(bytearry,int1,int2){
var result = this.read(bytearry,int1,int2);
console.log("HTTPS read result,bytearry,int1,int2=>",result,bytearry,int1,int2)
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log("bytearray contents=>", ByteString.of(bytearry).hex())
return result;
}


})
}


function hook_SSLsocket2android10(){
Java.perform(function(){
console.log(" hook_SSLsocket2")
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
Java.use("com.android.org.conscrypt.NativeCrypto").SSL_write.implementation = function(long,NS,fd,NC,bytearray,int1,int2,int3){
var result = this .SSL_write(long,NS,fd,NC,bytearray,int1,int2,int3);
console.log("SSL_write(long,NS,fd,NC,bytearray,int1,int2,int3),result=>",long,NS,fd,NC,bytearray,int1,int2,int3,result)
console.log(ByteString.of(bytearray).hex());
return result;
}
Java.use("com.android.org.conscrypt.NativeCrypto").SSL_read.implementation = function(long,NS,fd,NC,bytearray,int1,int2,int3){
var result = this .SSL_read(long,NS,fd,NC,bytearray,int1,int2,int3);
console.log("SSL_read(long,NS,fd,NC,bytearray,int1,int2,int3),result=>",long,NS,fd,NC,bytearray,int1,int2,int3,result)
console.log(ByteString.of(bytearray).hex());
return result;
}
})
}

function main(){
console.log("Main")
hook_socket();
hook_SSLsocketandroid8();
//hook_SSLsocket2android10();
}
setImmediate(main)

Hook KeyStore

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function printStack(str_tag)
{
var Exception= Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();

if (undefined == straces || null == straces)
{
return;
}

console.log("==" + str_tag + " Stack strat ===");
console.log("");

for (var i = 0; i < straces.length; i++)
{
var str = " " + straces[i].toString();
console.log(str);
}

console.log("");
console.log("===" + str_tag + " Stack end ===\r\n");
Exception.$dispose();
}
//过客户端校验服务器
function hook_KeyStore_load() {
// hook证书 自吐证书密码和内容保存在sdcard中
Java.perform(function () {
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
var myArray=new Array(1024);
var i = 0
for (i = 0; i < myArray.length; i++) {
myArray[i]= 0x0;
}
var buffer = Java.array('byte',myArray);

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(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

console.log("KeyStore.load1:", arg0);
this.load(arg0);
};
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
if (arg0){
var file = Java.use("java.io.File").$new("/sdcard/Download"+ String(arg0)+".p12");
var out = Java.use("java.io.FileOutputStream").$new(file);
var r;
while( (r = arg0.read(buffer)) > 0){
out.write(buffer,0,r)
}
console.log("save success!")
out.close()
}
this.load(arg0, arg1);
};

console.log("hook_KeyStore_load...");
});
}
//过证书绑定 ssl pinning 对证书在代码中进行额外校验,
function hook_ssl() {
// hook ssl 调用栈
Java.perform(function() {
var ClassName = "com.android.org.conscrypt.Platform";
var Platform = Java.use(ClassName);
var targetMethod = "checkServerTrusted";
var len = Platform[targetMethod].overloads.length;
console.log(len);
for(var i = 0; i<len; ++i) {
Platform[targetMethod].overloads[i].implementation = function () {
console.log("class:", ClassName, "target:", targetMethod, "i:", i, arguments);
printStack(ClassName + "." + targetMethod);
}
}
});
}

开启hook

1
2
3
frida -U -f cn.soulapp.android -l ssl.js  通过attach进行hook公钥加解密的框架层api,打印密码用于解开证书用
%resume 重新启动
frida -U -f cn.soulapp.android -l ssl.js --no-pause 通过spawn进行hook,证书公钥发送给服务器,从服务器获取session用私钥进行解密的api进行hook,属于自吐,查看terminal中的密码 }%2R+\OSsjpP!w%X

或者通过反编译拿到证书加载的位置,关键词PKCS12

image-20221026153832567

第二个红框中的load函数的第二个参数其实就是证书的密钥, 追根溯源, 我们可以知道v1参数是下图中调用的函数的返回值。

image-20221026153909376

image-20221026153923872

功能就是传递p0参数, 也就是说p0参数就是证书安装密码。想获取这个密码, 关键在于Auto_getValue函数。到这一步, 只要跟进Null_getStorePassword函数看看就好了。跟进去发现调用了native层的函数, 查看init函数中具体加载的是哪个so文件。

image-20221026154007458

用IDA反编译soul-netsdk之后, 搜索字符串”getStorePassword”, 就定位到函数getStorePassword上了, F5之后, 获得伪代码和密钥

image-20221026154113464

抓包

1
2
3
4
5
6
7
8
9
7z x soul_channel_soul.apk
tree -NCfhl|grep -i p12 找到证书位置tree -NCfhl|grep -i bks
或者objection -g cn.soulapp.android explore 如果报错查看frida的客户端和服务端版本是否匹配
android hooking watch class_method java.io.File$init --dump-args 如果找不到文件
exit并干掉app后
objection -g cn.soulapp.android explore --start-command "android hooking watch class_method java.io.File$init --dump-args"
du -h assets/client.p12
file assets/client.p12
thunar . 在windows上找到后双击导入私钥需要密码即刚才hook到的}%2R+\OSsjpP!w%X 该证书的内容即可以全部显示

安装证书

导入charles - Proxy- SSL Proxying Settings 添加客户端证书

自定义安全密码admin

安装客户端证书

输入KeyStore

启动ssl抓包,Charles将对任意服务器发送该客户端证书,意味着用app访问所有的服务器,后续关闭客户端证书的soulapp.cn,否则会误认为请求来自于soul, 测试登录

开启ssl

关闭soul证书

soul抓包成功

fiddler中可以使用openssl将.p12格式的证书转换成.cer/.der格式的证书。(.der和.cer格式的证书仅有文件头和文件尾不同)

下面的命令实现了证书的格式转换, .p12->.pem->.cer, 在生成.pem格式的证书之后, 需要输入证书的密码, 也就是我们上面逆向获取的证书密码,也就是我们上面逆向获取的证书密码。最后将ClientCertificate.cer移动到之前Fiddler弹窗出现的目录下, 也就是\Fiddler4下。

1
2
3
4
5
# 将.p12证书转换成.pem格式
$ openssl pkcs12 -in client.p12 -out ClientCertificate.pem -nodes
Enter Import Password:
# 将.pem证书转换成.cer格式
$ x509 -outform der -in ClientCertificate.pem -out ClientCertificate.cer

image-20221026154418966

趣充

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端口,即可拿到完整加密请求结果

image-20210707123859186

image-20220912112637488

滴答

启动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
2
3
4
objection -g cn.ticktick.task explore -s "android hooking watch class_method java.io.File.$init --dump-args --dump-backtrace --dump-return"  打开证书文件就初始化这个文件,hook该类,发送验证码
plugin wallbreaker classdump z1.g
plugin wallbreaker objectsearch z1.g
plugin wallbreaker objectdump --fullname 0x24e6

image-20220912194107785

image-20210707130643793

1
2
3
cd OkHttpLogger-Frida && frida -U -f cn.ticktick.task -l okhttp_poker.js 
%resume
find() 失败则不是ok3混淆,那么就是ok1

https://square.github.io/okhttp/4.x/okhttp/okhttp3/-certificate-pinner/

image-20210707132638475

1
2
3
4
5
6
7
8
9
10
11
android hooking list class_methods z1.g  其中有z1.g.a,在CertificatePinner中被混淆的
function killCertificatePinner(){
Java.perform(function(){
console.log(Beginning killCertificatePinner !...)
Java.use(z1.g).a.implementation = function(str,list){
console.log(called z1.g.a ~)
return ;
}
})
}
frida -U -f cn.ticktick.task -l quchong.js --no-pause 发送注册短信过掉z1.g.a的ok1中的证书

多重证书绑定

咪咕视频

登录抓包,SSL handshake with client failed: An unknown issue occurred processing the certificate (certificate_unknown)从抓包发现证书 绑定,可能客户端只信任信任的公钥签名,不信任就不允许,停止客户端访问的证书绑定。客户端发了,我们已经绕过了校验,把自己公钥发给charles,charles用自己私钥解开客户端的公钥发现不正常的结果。

1
2
3
4
5
6
dumpsys activity top  查看包名 com.ophone.reader.ui
objection -g com.ophone.reader.ui explore
android sslpinning disable 需要在启动时运行
objection -g com.ophone.reader.ui explore -s "android sslpinning disable" 在点我登录页面触发解绑定,如果崩溃
objection -g com.ophone.reader.ui explore 在点我登录时开始漫游
android sslpinning disable 解绑定后再获取验证码,再抓包

证书绑定的逻辑没有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
2
3
4
5
6
python r0capture.py -U -f com.ophone.reader.ui -v  尝试导出证书
frida -U -f com.ophone.reader.ui -l script.js --no-pause
adb shell 查看sdcard/Download下的证书
adb pull /sdcard/Download/ophone 下的证书导入到Charles的SSL Proxying Settings中,打开postern抓包
objection -g com.ophone.reader.ui explore 在点我登录时开始漫游
android sslpinning disable 解绑定后再获取验证码抓包即可获取passport.migu.cn:8443的包信息

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
2
3
4
cd utils/android
frida -U -f com.ophone.reader.ui -l multi_unpinning.js --no-pause
objection -g com.ophone.reader.ui explore -s "android hooking watch class_method java.io.File.\$init --dump-args --dump-backtrace --dump-return" 查看证书
frida -U -f com.ophone.reader.ui -l trace.js --no-pause -o ophone.txt 修改trace.js中traceClass("java.io.File"),在traceClass中修改targets=[]只trace init方法,在traceMethod中打开调用栈android.util.log,发送验证码后查看文件,搜索cacert查看调用栈

image-20210528115202167

北京银行

1
2
3
4
python r0capture.py -U -f com.bankofbeijing.mobilebanking -v
./hluda-server-14.2.1-android-arm64
frida -U -f com.bankofbeijing.mobilebanking -l script.js --no-pause -o bjbank.txt
frida -U -f com.bankofbeijing.mobilebanking -l trace.js --no-pause -o bjbank2.txt 打开traceClass("java.security.KeyStore$PrivateKeyEntry")

加固厂商自定义开发的证书绑定对抗很难被攻克。终极解决方案就是修改安卓底层aosp源码,将发送请求时打印log后,重编译成镜像刷机,比如项目crypto_filter_aosp,让整个系统都变成一个抓包工具。

参考:

文章作者: J
文章链接: http://onejane.github.io/2022/10/25/抓包对抗/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏