抓包环境与hook

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

1、http://aospxref.com/
优点:更新速度快
缺点:历史版本较少

2、http://androidxref.com/
优点:历史版本较多
缺点:更新速度慢

Http抓包

基本原理

美团点评移动网络优化实践在http套ssl且加密内容成为https

image-20210219000224919

平时我们碰到的http和https都在应用层,socks在会话层,tcp和udp在传输层。ip在网络层。代理都是通过给wifi设置http代理的方式进行抓包,只是在应用层抓包,所以会被很轻易的检测到和绕过的。 很多应用会通过 System.getProperty(“http.proxyHost”); System.getProperty(“http.proxyPort”); 这两个API来查看当前系统是否挂了http代理,会很轻松的让你的抓包失效。 所以我们需要换一种方式来设置代理。就是设置vpn代理,vpn是属于网络层的,设置了vpn后,你的手机上ifconfig后会多一个接口,等于加了一个虚拟网卡,所有的流量都会从这走。应用层和传输层的请求都可以拿到,还不会被上面提及的两个api所检测。

七层网

图片视频处于应用层,http协议横跨应用层–表示层–会话层,socks、ssl属于会话层,IP属于网络层,端口属于传输层

应用层Https抓包的根本原理

HTTPS是包裹在SSL协议里的HTTP,APP-Charles客户端校验服务端,Charles-服务器是服务端校验客户端,400 Bad Request,No required SSL certificate was sent

抓包原理

在开始 APP 分析时候我们提到使用 Charles 抓不到包,设置了 SSL 代理也没有用,当我们反编译分析脱壳后的dex找到构造请求的地方时,发现了真相,和 SSL 没有关系,是因 openConnection(Proxy.NO_PROXY),使用vpn抓包后系统将多一个Interface,设置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。Bypass也很简单,hook该api使其返回“rmnet_data1”,即可达到过vpn检测目的。

检测VPN的API

环境搭建

tar zxf charles-proxy-4.6.1_amd64.tar.gz && ./charles 通过注册码注册或生成加权jar包破解,在Proxy中External Proxy Setting配置代理

image-20220910100242242

adb install 0714com.tunnelworkshop.postern_2018-10-07.apk

网络设置

主机连接无线网,虚拟机网络连接修改NAT设置桥接模式即可,不用勾选复制物理网络连接状态,保证手机192.168.0.100和虚拟机192.168.0.109和主机192.168.0.108互相ping通

虚拟网络设置

配置Postern并启动VPN

抓取所有应用层和传输层的包

Postern配置代理 Postern配置规则

如需安装https,需要开启SSL Proxying Settings-Enable SSL Proxying,设置规则*.*

配置Burpsuite

java -jar burp-loader-keygen.jar - Run - 复制License -EnterLicense Key - Manual Activition - Copy Request 到 Activation Request - 复制Activition Response到Paster response,在User options中设置Socks Proxy实现科学代理。

配置不限ip连接

配置Postern通过代理192.168.0.109:8080并重启VPN

burp抓包设置过滤

openssl x509 -inform DER -in burpder1545.der -out burpder1545.pem 把der转化成pem,然后adb push到/sdcard/Download文件夹下,然后设置→安全性和位置信息→加密与凭据→从存储设备安装,选择pem证书,即可安装到用户用户信任区

image-20220911162723623

img

Https和Socks5抓包

image-20220910103407163

抓住包出在明文状态的一切时机。种类:Http框架的hook,系统框架hook,中间人抓包。

http+加密+认证+完整性保护=https是身披ssl的http

HTTP未加密主要有这些不足

  • 通信使用明文(不加密),内容可能会被窃听
  • 不验证通信方的身份,因此有可能遭遇伪装 DNS劫持->GFW翻墙
  • 无法证明报文的完整性,所以有可能已遭篡改 运营商劫持->弹窗广告

cat /etc/resolv.conf 查看主机dns服务器

通信的加密
HTTP协议中没有加密机制,但可以通过和SSL( Secure Socket Layer,安全套接层)或TLS( Transport Layer Security,安全层传输协议)的组合使用,加密HTTP的通信内容。用SSL建立安全通信线路之后,就可以在这条线路上进行HTTP通信了。与SSL组合使用的HTTP被称为Https(HTTP Secure,超文本传输安全协议)或http over ssl。

内容的加密
由于HTTP协议中没有加密机制,那么就对HTTP协议传输的内容本身加密。即把HTTP报文里所含的内容进行加密处理在这种情况下,客户端需要对HTTP报文进行加密处理后再发送请求。诚然,为了做到有效的内容加密,前提是要求客户端和服务器同时具备加密和解密机制。主要应用在Web服务中。有一点必须引起注意,由于该方式不同于SSL或TLS将整个通信线路加密处理,所以内容仍有被篡改的风险。

设置-安全性和位置信息-加密与凭据-信任的凭据 查看安装在手机上的信任凭据

https与http
1
2
3
4
objection -g com.android.settings explore
memory list modules
memory list exports libssl.so
memory list exports libssl.so --json "/root/libssl.txt" 搜索rsa md5 aes sha非对称加解密

HTTPS采用共享秘钥和公开秘钥混合加密

img

开启https

虚拟机启动charles,手机wifi设置代理192.168.0.109:8888,访问chls.pro/ssl安装证书

关闭代理,启动Postern代理192.168.0.109:8888并开启VPN

chales开启ssl抓包

adb install iqiyi_696.apk启动爱奇艺抓包,虽然浏览器能抓到,但是依旧app抓不到,基于android 8.1+Magisk ,需要把个人证书放到系统根目录

1
2
3
4
5
cd /data/misc/user/0/cacerts-added/
mount -o remount,rw /
chmod 777 *
cp * /etc/security/cacerts/
mount -o remount,ro /
证书放入根目录

Android将证书放入根目录

1
2
3
4
5
cd /data/misc/user/0/cacerts-added/
mount -o rw,remount /system
chmod 777 *
cp * /etc/security/cacerts/
mount -o ro,remount /system

开启socks5

socket本质:收发包的接口,一条跑着RAW DATA的通道,纯binary,wireshark抓包本质上不是中间人抓包,网卡流量的dump转储下来。应用领域SSL+HTTP,SMPT/POP/IMAP,protobuf

一文搞懂TCP与UDP的区别

通过抓包和一点点经验分析携程的协议

安卓应用层抓包通杀脚本

image-20220912105555810

img

逆向XposedHook模块分析Hook检测过Vpn抓包原理!

某抢票app逆向续篇之干掉vpn抓包检测

若依旧抓不全包,则使用Postern开启socks5抓包并配置规则开启VPN,charles配置socks抓包,本质在tcp层抓包。

Postern配置socks5 charles配置socks5 socks5抓包

Soul登录抓包

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

soul抓包报错

hook

frida两种模式:attach在app启动之后进行hook, spawn未启动时就通过包名启动apk进行hook

1
2
3
android hooking search classes ssl
android hooking watch class javax.net.ssl.SSLSession 文本中拼上前缀
objection -g x.x.x explore -c a.txt

完整实现

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)

ssl.js

过自签名证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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(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);
this.load(arg0, arg1);
};

console.log("hook_KeyStore_load...");
});
}
setImmediate(hook_KeyStore_load)

开启hook

1
2
3
4
5
6
7
./data/local/tmp/fs128arm64  开启frida-server
pyenv local 3.8.0
frida --version 版本一致为12.8.0
top 查看soul的包名为cn.soulapp.android
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

查看KeyStore

搜索证书

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

charls将对任意服务器发送该客户端证书 意味着用app访问所有的服务器 测试登录

soul抓包成功

统一代码查询

1
2
3
4
5
pm -l |grep -i ncs  查看包名
frida -U -f com.ninemax.ncsearchnew -l hook_keystore.js --no-pause 证书下载到/sdcard/Download
objection -g com.ninemax.ncsearchnew 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

image-20220912171345204

国外app

proxy-External proxy servers设置代理

访问ip111.cn通过测试

Hook抓包

  • 缺点:不如抓包软件全面

  • 优点:无视证书、基于HOOK直接得到参数、打调用栈得到参数的构成和来源

一般HTTPS:大部分只采用客户端校验服务器 iqiyi
双向绑定:很少会有服务器校验客户端 soul
SSL Cert Pinning:更少:趣充

adb install movetv.apk 使用Postern和Charles开启socks5即可完整抓到注册登录的请求,部分https无法抓到

安装证书chls.pro/ssl并把把个人证书放到系统根目录,启动ssl抓包,关闭客户端证书的soulapp.cn,否则会误认为请求来自于soul,即可抓到https包。

开启ssl关闭soul证书

脱壳

1
2
3
./data/local/tmp/fs128arm64 启动frida-server
objection -g com.cz.babySister explore -P ~/.objection/plugins
plugin dexdump dump
movetv脱壳

批量hook

1
2
3
4
5
6
7
8
android hooking list classes    列出所有类
android hooking list activities 查看所有activity
cat ~/.objection/objection.log|grep -i httpurl
cat objection.log|grep -i http 拿到所有关于http相关类,并将前面都加入命令android hooking wathc class存入hooklist.txt
objection -g com.android.settings explore -c "hooklist.txt" 加载命令文件即可批量hook
dumpsys activity top|grep baby 在shell中拿到包当前所在activity
plugin wallbreaker objectsearch com.cz.babySister.activity.LoginActivity --fullname 搜索类信息地址
android heap search instances com.cz.babySister.activity.LoginActivity 查看activity属性信息

Zentracer

1
2
3
4
5
6
./data/local/tmp/fs1428arm64 开始frida-server
git clone https://github.com/hluwa/ZenTracer
cd ZenTracer && pyenv local 3.8.5
python -m pip install --upgrade pip
pip install PyQt5
python ZenTracer.py 开始hook设置Match RegEx并开启Start

Zentracer开启hook

登录注册
1
android hooking watch class_method java.net.HttpURLConnection.getFollowRedirects --dump-args --dump-backtrace --dump-return  hook方法打印调用栈登录定位HTTPURLConnection.getFollowRedirects在com.cz.babySister.c.a.a调用

定位函数收发包类

案例

春水堂

adb install 春水堂_2.1.0.0强混淆.apk

  1. 通过objection -g org.sfjboldyvukzzlpp explore内存漫游后访问视频,cat objection.log|grep -i okhttp3发现根本找不到相关http类信息
  2. 通过apktool d 春水堂_2.1.0.0强混淆.apk解包后搜索ok3信息cd 春水堂_2.1.0.0强混淆 && grep -ril "okhttp3" *找到相关smali
1
2
3
4
5
6
./data/local/tmp/fs128arm64  启动 frida
pyenv local 3.8.0
git clone https://github.com/siyujie/OkHttpLogger-Frida.git
cd OkHttpLogger-Frida && adb push okhttpfind.dex /data/local/tmp 将dex文件放入手机并chmod 777
frida -U -f org.sfjboldyvukzzlpp -l okhttp_poker.js --no-pause
find() 查看是否使用okhttp并查看混淆结果
查看okhttp混淆结果

将Find Result下的内容复制到okhttp_poker.js开头,替换原有混淆类名

替换混淆结果
1
2
frida -U -f org.sfjboldyvukzzlpp -l okhttp_poker.js --no-pause  退出后重新frida调用
hold() 通杀所有okhttp混淆参数结果
通杀okhttp

爱奇艺

charles证书放到根目录,可以通过magisk的move Certificates安装重启。客户端校验服务器。

hook

1
2
3
4
git clone https://github.com/BigFaceCat2017/frida_ssl_logger.git  框架层的conscrypt本质上是走的native层的ssl抓包log
adb install iqiyi_696.apk
electron-ssr && proxychains pip install hexdump 安装hexdump
python ssl_logger.py -U -f com.qiyi.video >> iqiyi.txt 启动iqiyi并开启hook所有ssl信息保存到文件

ssl抓包iqiyi

ddms

xposed安装XAppDebug模块,开启debug,选中春水堂APP开启调试

XAppDebug调试

~/Android/Sdk/tools/monitor 启动ddms

选中进程进行方法剖析

使用Trace based profiling,登录后停止方法剖析,搜索HttpURLConnection

趣充

adb install -r -t quchong.apk

./charles 启动charles抓包注册获取验证码,设置了no proxy防抓包

image-20210707100228016

服务器校验客户端证书 : 400 No required SSL certificate was sent

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);

frida -UF -l quchong.js 抓包获取注册验证码

frida -UF -l hookSocket.js -o quchong.txt

ps -ef|grep -i quchong 获取包名

1
2
3
4
5
6
7
8
9
objection -g com.whwy.equchong explore
android hooking search classes okhttp
git clone https://github.com/siyujie/OkHttpLogger-Frida.git
adb push okhttpfind.dex /data/local/tmp && chmod 777 okhttpfind.dex
frida -U -l okhttp_poker.js -f com.whwy.equchong --no-pause
find() 查看是否被混淆后的okhttp的参数拷贝到okhttp_poker.js上面定义参数
frida -UF -l okhttp_poker.js -o quchong.txt attach模式操作发送验证码前启动hook
frida -U -l okhttp_poker.js -f com.whwy.equchong --no-pause -o qichong.txt spawn模式
hold() 发送验证码抓包

image-20210707105723540

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
//过客户端校验服务器
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);
}
}
});
}

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
android hooking list class_methods z1.g  其中有z1.g.a,在CertificatePinner中被混淆的
1
2
3
4
5
6
7
8
9
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中的证书

抓包总结

kali nethunter

apt install jnettop nethogs htop 查看请求ip端口

优点:直观、所见即所得
缺点:没有内容,粒度太粗,简单看下

wireshark 使用NetHunter-Kex Manager-SETUP LOCAL SERVER 使用USER为root Start SERVER,启动NetHunter Kex的VNC连接界面kali,VNC Viewer也可以通过ip连接,图形化抓包

优点:可以转储内容scp root@192.186.0.2L/root/a.pcap ./,存下来稍后分析
缺点:只能看明文,不能解加密协议

ARM设备武器化指南·破·Kali.Nethunter.2020a.上手实操

termux

adb install termux && pkg update && pkg install tcpdump && ln -s /data/data/com.termux/files/usr/bin/tcpdump /system/bin 并remount

安卓7remount: mount -o rw,remount /system

安卓8remount:mount -o remount,rw /system

mount -o remount.rw / 重新挂载,使用tcpdump开启抓包

优点:不需要刷Nethunter
缺点:没有界面

hook

1
2
3
4
5
6
7
objection -g com.onejane.httpsocket explore -P ~/.objection/plugins 
android hooking search classes socket 将打印的类存入hookSocket.txt
sed -i -e 's/^/android hooking watch class_method /' hookSocket.txt 在以上的类前加入android hooking watch class_method
sed -i s/$/".\$init"/ hookSocket.txt 在以上的类后加入.$init
plugin wallbreaker objectsearch java.net.Socket 内存查找类
plugin wallbreaker objectdump --fullname 0x21f2 打印内存中的类
plugin wallbreaker classdump --fullname java.net.InetAddress

内存漫游

1
objection -g com.onejane.httpsocket explore -c ~/root/Desktop/hookSocket.txt  -P ~/.objection/plugins  批量hook,如有报错就删除报错类

分别使用http和https运行HttpSocket项目,通过frida批量hook以上整理的hookSocket.txt中的类

HTTP
java.net.InetSocketAddress.InetSocketAddress(www.baidu.com/180.101.49.12, 80)
java.net.InetSocketAddress$InetSocketAddressHolder.InetSocketAddress$InetSocketAddressHolder((none), www.baidu.com/180.101.49.12, 80, (none))
java.net.InetSocketAddress.InetSocketAddress(/192.168.0.2, 43066)
java.net.InetSocketAddress$InetSocketAddressHolder.InetSocketAddress$InetSocketAddressHolder((none), /192.168.0.2, 43066, (none))
java.net.SocketInputStream.SocketInputStream(Socket[addr=www.baidu.com/180.101.49.12,port=80,localport=43066])
java.net.SocketOutputStream.SocketOutputStream(Socket[addr=www.baidu.com/180.101.49.12,port=80,localport=43066])
HTTPS
java.net.InetSocketAddress.InetSocketAddress(www.baidu.com/180.101.49.12, 443)
java.net.Socket$2.Socket$2(Socket[address=www.baidu.com/180.101.49.12,port=443,localPort=44405])
java.net.SocketInputStream.SocketInputStream(Socket[addr=www.baidu.com/180.101.49.12,port=443,localport=44405])
java.net.SocketOutputStream.SocketOutputStream(Socket[addr=www.baidu.com/180.101.49.12,port=443,localport=44405])
com.android.org.conscrypt.ConscryptFileDescriptorSocket.ConscryptFileDescriptorSocket(Socket[address=www.baidu.com/180.101.49.12,port=443,localPort=44405], www.baidu.com, 443, true, com.android.org.conscrypt.SSLParametersImpl@2ccad02)
com.android.org.conscrypt.OpenSSLSocketImpl.OpenSSLSocketImpl(Socket[address=www.baidu.com/180.101.49.12,port=443,localPort=44405], www.baidu.com, 443, true)
com.android.org.conscrypt.AbstractConscryptSocket.AbstractConscryptSocket(Socket[address=www.baidu.com/180.101.49.12,port=443,localPort=44405], www.baidu.com, 443, true)
com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.ConscryptFileDescriptorSocket$SSLOutputStream(SSL socket over Socket[address=www.baidu.com/180.101.49.12,port=443,localPort=44405])
com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.ConscryptFileDescriptorSocket$SSLInputStream(SSL socket over Socket[address=www.baidu.com/180.101.49.12,port=443,localPort=44405])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hook_Address(){
Java.perform(function(){
Java.use("java.net.InetSocketAddress").$init.overload('java.net.InetAddress', 'int').implementation = function(addr,int){
var result = this.$init(addr,int)
if(addr.isSiteLocalAddress()){ // 判断是不是本地地址
console.log("Local address => ",addr.toString()," port is => ",int)
}else{
console.log("Server address => ",addr.toString()," port is => ",int)
}

return result;
}

})
}

frida -U -f com.onejane.httpsocket -l socketAddress.js –no-pause

frida -U com.cz.babySister -l socketAddress.js –no-pause -o /root/Desktop/babySisterLogin.txt 使用attach模式抓移动tv登录包http

frida -U com.ilulutv.fulao2 -l socketAddress.js –no-pause -o /root/Desktop/fulao2Login.txt 使用attach模式抓fulao2登录包https

frida -U com.ilulutv.fulao2 -l hookEvent.js 点击得到所在event的类,点击登录触发AccountActivity

1
2
3
4
5
6
7
8
9
10
11
android hooking search classes cipher  直接搜索加密相关类,并将所有类加入txt文件进行批量hook
objection -g com.onejane.httpsocket explore -c ~/root/Desktop/hookCipher.txt
android hooking watch class_method javax.crypto.Cipher.createCipher hook系统的cipher类
android hooking watch class_method net.idik.lib.cipher.so.encrypt.AESEncryptor.decrypt
android hooking watch class_method javax.crypto.Cipher.doFinel 根据堆栈找到登录时触发的app函数
android hooking watch class com.ilulutv.fulao2.other.i.b --dump-args --dump-backtrace --dump-return
android hooking watch class_method com.ilulutv.fulao2.other.i.b.a --dump-args --dump-backtrace --dump-return
android hooking watch class_method com.ilulutv.fulao2.other.i.b.b --dump-args --dump-backtrace --dump-return
android hooking watch class_method com.ilulutv.fulao2.other.i.b.c --dump-args --dump-backtrace --dump-return
android hooking watch class_method com.ilulutv.fulao2.other.i.b.d --dump-args --dump-backtrace --dump-return
android hooking watch class_method com.ilulutv.fulao2.other.i.b.e --dump-args --dump-backtrace --dump-return

手机上抓:优点:无法对抗,全部能抓,没有抓不到的包
hook抓包:
- 优点1:为所欲为,可以对包的内容进行进一步的更改和定制;
- 优点2:抓包全面,直接就是明文,不需要解协议(无需绕过证书绑定,进入ssl之前已经抓到包)
- 缺点:可能会不全、可能会漏。hook点是有限的,万一它在用奇葩的框架做网络传输就会漏掉
Charles+Postern:协议层抓包:
- 优点:全面。已经解好了协议,HTTP、WebSocket直接解好。
- 缺点:配置证书稍微麻烦,解不了纯Socket。

抓包通杀

git clone https://github.com/BigFaceCat2017/frida_ssl_logger.git

1
2
3
4
5
python ssl_logger.py  -U -v com.iqiyi.video -p iqiyi.pcap  爱奇艺获取验证码抓包
plugin wallbreaker objectsearch java.net.SocketOutputStream
plugin wallbreaker objectdump --fullname 0x4c16 看到socket类中地址和ip
android hooking search classes socket 将相关类拷贝到文件中前面加上android hooking watch class
objection -g com.roysue.httpsocket explore -c tracesocket.txt 哪个类报错干掉哪个类即可

r0capture.py

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# Copyright 2017 Google Inc. All Rights Reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Decrypts and logs a process's SSL traffic.
Hooks the functions SSL_read() and SSL_write() in a given process and logs the
decrypted data to the console and/or to a pcap file.
Typical usage example:
ssl_log("wget", "log.pcap", True)
Dependencies:
frida (https://www.frida.re/):
sudo pip install frida
hexdump (https://bitbucket.org/techtonik/hexdump/) if using verbose output:
sudo pip install hexdump
"""

__author__ = "geffner@google.com (Jason Geffner)"
__version__ = "2.0"

"""
# r0capture

ID: r0ysue

安卓应用层抓包通杀脚本

https://github.com/r0ysue/r0capture

## 简介

- 仅限安卓平台,测试安卓7、8、9、10 可用 ;
- 无视所有证书校验或绑定,无视任何证书;
- 通杀TCP/IP四层模型中的应用层中的全部协议;
- 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
- 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
"""


# Windows版本需要安装库:
# pip install 'win_inet_pton'
# pip install hexdump
import argparse
import os
import platform
import pprint
import random
import signal
import socket
import struct
import time
import sys

import frida

try:
if os.name == 'nt':
import win_inet_pton
except ImportError:
# win_inet_pton import error
pass

try:
import hexdump # pylint: disable=g-import-not-at-top
except ImportError:
pass
try:
from shutil import get_terminal_size as get_terminal_size
except:
try:
from backports.shutil_get_terminal_size import get_terminal_size as get_terminal_size
except:
pass


try:
import click
except:
class click:
@staticmethod
def secho(message=None, **kwargs):
print(message)
@staticmethod
def style(**kwargs):
raise Exception("unsupported style")

banner = """
--------------------------------------------------------------------------------------------
.oooo. .
d8P'`Y8b .o8
oooo d8b 888 888 .ooooo. .oooo. oo.ooooo. .o888oo oooo oooo oooo d8b .ooooo.
`888""8P 888 888 d88' `"Y8 `P )88b 888' `88b 888 `888 `888 `888""8P d88' `88b
888 888 888 888 .oP"888 888 888 888 888 888 888 888ooo888
888 `88b d88' 888 .o8 d8( 888 888 888 888 . 888 888 888 888 .o
d888b `Y8bd8P' `Y8bod8P' `Y888""8o 888bod8P' "888" `V88V"V8P' d888b `Y8bod8P'
888
o888o
https://github.com/r0ysue/r0capture
--------------------------------------------------------------------------------------------\n
"""


def show_banner():
colors = ['bright_red', 'bright_green', 'bright_blue', 'cyan', 'magenta']
try:
click.style('color test', fg='bright_red')
except:
colors = ['red', 'green', 'blue', 'cyan', 'magenta']
try:
columns = get_terminal_size().columns
if columns >= len(banner.splitlines()[1]):
for line in banner.splitlines():
click.secho(line, fg=random.choice(colors))
except:
pass

# ssl_session[<SSL_SESSION id>] = (<bytes sent by client>,
# <bytes sent by server>)
ssl_sessions = {}


def ssl_log(process, pcap=None, host=False, verbose=False, isUsb=False, ssllib="", isSpawn=True, wait=0):
"""Decrypts and logs a process's SSL traffic.
Hooks the functions SSL_read() and SSL_write() in a given process and logs
the decrypted data to the console and/or to a pcap file.
Args:
process: The target process's name (as a string) or process ID (as an int).
pcap: The file path to which the pcap file should be written.
verbose: If True, log the decrypted traffic to the console.
Raises:
NotImplementedError: Not running on a Linux or macOS system.
"""

# if platform.system() not in ("Darwin", "Linux"):
# raise NotImplementedError("This function is only implemented for Linux and "
# "macOS systems.")

def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
dst_addr, dst_port, data):
"""Writes the captured data to a pcap file.
Args:
pcap_file: The opened pcap file.
ssl_session_id: The SSL session ID for the communication.
function: The function that was intercepted ("SSL_read" or "SSL_write").
src_addr: The source address of the logged packet.
src_port: The source port of the logged packet.
dst_addr: The destination address of the logged packet.
dst_port: The destination port of the logged packet.
data: The decrypted packet data.
"""
t = time.time()

if ssl_session_id not in ssl_sessions:
ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),
random.randint(0, 0xFFFFFFFF))
client_sent, server_sent = ssl_sessions[ssl_session_id]

if function == "SSL_read":
seq, ack = (server_sent, client_sent)
else:
seq, ack = (client_sent, server_sent)

for writes in (
# PCAP record (packet) header
("=I", int(t)), # Timestamp seconds
("=I", int((t * 1000000) % 1000000)), # Timestamp microseconds
("=I", 40 + len(data)), # Number of octets saved
("=i", 40 + len(data)), # Actual length of packet
# IPv4 header
(">B", 0x45), # Version and Header Length
(">B", 0), # Type of Service
(">H", 40 + len(data)), # Total Length
(">H", 0), # Identification
(">H", 0x4000), # Flags and Fragment Offset
(">B", 0xFF), # Time to Live
(">B", 6), # Protocol
(">H", 0), # Header Checksum
(">I", src_addr), # Source Address
(">I", dst_addr), # Destination Address
# TCP header
(">H", src_port), # Source Port
(">H", dst_port), # Destination Port
(">I", seq), # Sequence Number
(">I", ack), # Acknowledgment Number
(">H", 0x5018), # Header Length and Flags
(">H", 0xFFFF), # Window Size
(">H", 0), # Checksum
(">H", 0)): # Urgent Pointer
pcap_file.write(struct.pack(writes[0], writes[1]))
pcap_file.write(data)

if function == "SSL_read":
server_sent += len(data)
else:
client_sent += len(data)
ssl_sessions[ssl_session_id] = (client_sent, server_sent)

def on_message(message, data):
"""Callback for errors and messages sent from Frida-injected JavaScript.
Logs captured packet data received from JavaScript to the console and/or a
pcap file. See https://www.frida.re/docs/messages/ for more detail on
Frida's messages.
Args:
message: A dictionary containing the message "type" and other fields
dependent on message type.
data: The string of captured decrypted data.
"""
if message["type"] == "error":
pprint.pprint(message)
os.kill(os.getpid(), signal.SIGTERM)
return
if len(data) == 1:
print(message["payload"]["function"])
print(message["payload"]["stack"])
return
p = message["payload"]
if verbose:
src_addr = socket.inet_ntop(socket.AF_INET,
struct.pack(">I", p["src_addr"]))
dst_addr = socket.inet_ntop(socket.AF_INET,
struct.pack(">I", p["dst_addr"]))
print("SSL Session: " + p["ssl_session_id"])
print("[%s] %s:%d --> %s:%d" % (
p["function"],
src_addr,
p["src_port"],
dst_addr,
p["dst_port"]))
hexdump.hexdump(data)
print(p["stack"])
if pcap:
log_pcap(pcap_file, p["ssl_session_id"], p["function"], p["src_addr"],
p["src_port"], p["dst_addr"], p["dst_port"], data)

if isUsb:
try:
device = frida.get_usb_device()
except:
device = frida.get_remote_device()
else:
if host:
manager = frida.get_device_manager()
device = manager.add_remote_device(host)
else:
device = frida.get_local_device()

if isSpawn:
pid = device.spawn([process])
time.sleep(1)
session = device.attach(pid)
time.sleep(1)
device.resume(pid)
else:
print("attach")
session = device.attach(process)
if wait > 0:
print("wait for {} seconds".format(wait))
time.sleep(wait)

# session = frida.attach(process)

# pid = device.spawn([process])
# pid = process
# session = device.attach(pid)
# device.resume(pid)
if pcap:
pcap_file = open(pcap, "wb", 0)
for writes in (
("=I", 0xa1b2c3d4), # Magic number
("=H", 2), # Major version number
("=H", 4), # Minor version number
("=i", time.timezone), # GMT to local correction
("=I", 0), # Accuracy of timestamps
("=I", 65535), # Max length of captured packets
("=I", 228)): # Data link type (LINKTYPE_IPV4)
pcap_file.write(struct.pack(writes[0], writes[1]))

with open("./script.js", encoding="utf-8") as f:
_FRIDA_SCRIPT = f.read()
# _FRIDA_SCRIPT = session.create_script(content)
# print(_FRIDA_SCRIPT)
script = session.create_script(_FRIDA_SCRIPT)
script.on("message", on_message)
script.load()

if ssllib != "":
script.exports.setssllib(ssllib)

print("Press Ctrl+C to stop logging.")

def stoplog(signum, frame):
print('You have stoped logging.')
session.detach()
if pcap:
pcap_file.flush()
pcap_file.close()
exit()
signal.signal(signal.SIGINT, stoplog)
signal.signal(signal.SIGTERM, stoplog)
sys.stdin.read()

if __name__ == "__main__":
show_banner()
class ArgParser(argparse.ArgumentParser):

def error(self, message):
print("ssl_logger v" + __version__)
print("by " + __author__)
print("Modified by BigFaceCat")
print("Error: " + message)
print()
print(self.format_help().replace("usage:", "Usage:"))
self.exit(0)


parser = ArgParser(
add_help=False,
description="Decrypts and logs a process's SSL traffic.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=r"""
Examples:
%(prog)s -pcap ssl.pcap openssl
%(prog)s -verbose 31337
%(prog)s -pcap log.pcap -verbose wget
%(prog)s -pcap log.pcap -ssl "*libssl.so*" com.bigfacecat.testdemo
""")

args = parser.add_argument_group("Arguments")
args.add_argument("-pcap", '-p', metavar="<path>", required=False,
help="Name of PCAP file to write")
args.add_argument("-host", '-H', metavar="<192.168.1.1:27042>", required=False,
help="connect to remote frida-server on HOST")
args.add_argument("-verbose","-v", required=False, action="store_const", default=True,
const=True, help="Show verbose output")
args.add_argument("process", metavar="<process name | process id>",
help="Process whose SSL calls to log")
args.add_argument("-ssl", default="", metavar="<lib>",
help="SSL library to hook")
args.add_argument("--isUsb", "-U", default=False, action="store_true",
help="connect to USB device")
args.add_argument("--isSpawn", "-f", default=False, action="store_true",
help="if spawned app")
args.add_argument("-wait", "-w", type=int, metavar="<seconds>", default=0,
help="Time to wait for the process")

parsed = parser.parse_args()
ssl_log(
int(parsed.process) if parsed.process.isdigit() else parsed.process,
parsed.pcap,
parsed.host,
parsed.verbose,
isUsb=parsed.isUsb,
isSpawn=parsed.isSpawn,
ssllib=parsed.ssl,
wait=parsed.wait
)

script.js

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/**
* Initializes 'addresses' dictionary and NativeFunctions.
*/
"use strict";
rpc.exports = {
setssllib: function (name) {
console.log("setSSLLib => " + name);
libname = name;
initializeGlobals();
return;
}
};

var addresses = {};
var SSL_get_fd = null;
var SSL_get_session = null;
var SSL_SESSION_get_id = null;
var getpeername = null;
var getsockname = null;
var ntohs = null;
var ntohl = null;
var SSLstackwrite = null;
var SSLstackread = null;

var libname = "*libssl*";

function uuid(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [], i;
radix = radix || chars.length;

if (len) {
// Compact form
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
} else {
// rfc4122, version 4 form
var r;

// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';

// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}

return uuid.join('');
}
function return_zero(args) {
return 0;
}
function initializeGlobals() {
var resolver = new ApiResolver("module");
var exps = [
[Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android
[Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]]
];
// console.log(exps)
for (var i = 0; i < exps.length; i++) {
var lib = exps[i][0];
var names = exps[i][1];
for (var j = 0; j < names.length; j++) {
var name = names[j];
// console.log("exports:" + lib + "!" + name)
var matches = resolver.enumerateMatchesSync("exports:" + lib + "!" + name);
if (matches.length == 0) {
if (name == "SSL_get_fd") {
addresses["SSL_get_fd"] = 0;
continue;
}
throw "Could not find " + lib + "!" + name;
}
else if (matches.length != 1) {
// Sometimes Frida returns duplicates.
var address = 0;
var s = "";
var duplicates_only = true;
for (var k = 0; k < matches.length; k++) {
if (s.length != 0) {
s += ", ";
}
s += matches[k].name + "@" + matches[k].address;
if (address == 0) {
address = matches[k].address;
}
else if (!address.equals(matches[k].address)) {
duplicates_only = false;
}
}
if (!duplicates_only) {
throw "More than one match found for " + lib + "!" + name + ": " + s;
}
}
addresses[name] = matches[0].address;
}
}
if (addresses["SSL_get_fd"] == 0) {
SSL_get_fd = return_zero;
} else {
SSL_get_fd = new NativeFunction(addresses["SSL_get_fd"], "int", ["pointer"]);
}
SSL_get_session = new NativeFunction(addresses["SSL_get_session"], "pointer", ["pointer"]);
SSL_SESSION_get_id = new NativeFunction(addresses["SSL_SESSION_get_id"], "pointer", ["pointer", "pointer"]);
getpeername = new NativeFunction(addresses["getpeername"], "int", ["int", "pointer", "pointer"]);
getsockname = new NativeFunction(addresses["getsockname"], "int", ["int", "pointer", "pointer"]);
ntohs = new NativeFunction(addresses["ntohs"], "uint16", ["uint16"]);
ntohl = new NativeFunction(addresses["ntohl"], "uint32", ["uint32"]);
}
initializeGlobals();

function ipToNumber(ip) {
var num = 0;
if (ip == "") {
return num;
}
var aNum = ip.split(".");
if (aNum.length != 4) {
return num;
}
num += parseInt(aNum[0]) << 0;
num += parseInt(aNum[1]) << 8;
num += parseInt(aNum[2]) << 16;
num += parseInt(aNum[3]) << 24;
num = num >>> 0;//这个很关键,不然可能会出现负数的情况
return num;
}

/**
* Returns a dictionary of a sockfd's "src_addr", "src_port", "dst_addr", and
* "dst_port".
* @param {int} sockfd The file descriptor of the socket to inspect.
* @param {boolean} isRead If true, the context is an SSL_read call. If
* false, the context is an SSL_write call.
* @return {dict} Dictionary of sockfd's "src_addr", "src_port", "dst_addr",
* and "dst_port".
*/
function getPortsAndAddresses(sockfd, isRead) {
var message = {};
var src_dst = ["src", "dst"];
for (var i = 0; i < src_dst.length; i++) {
if ((src_dst[i] == "src") ^ isRead) {
var sockAddr = Socket.localAddress(sockfd)
}
else {
var sockAddr = Socket.peerAddress(sockfd)
}
if (sockAddr == null) {
// 网络超时or其他原因可能导致socket被关闭
message[src_dst[i] + "_port"] = 0
message[src_dst[i] + "_addr"] = 0
} else {
message[src_dst[i] + "_port"] = (sockAddr.port & 0xFFFF)
message[src_dst[i] + "_addr"] = ntohl(ipToNumber(sockAddr.ip.split(":").pop()))
}
}
return message;
}
/**
* Get the session_id of SSL object and return it as a hex string.
* @param {!NativePointer} ssl A pointer to an SSL object.
* @return {dict} A string representing the session_id of the SSL object's
* SSL_SESSION. For example,
* "59FD71B7B90202F359D89E66AE4E61247954E28431F6C6AC46625D472FF76336".
*/
function getSslSessionId(ssl) {
var session = SSL_get_session(ssl);
if (session == 0) {
return 0;
}
var len = Memory.alloc(4);
var p = SSL_SESSION_get_id(session, len);
len = Memory.readU32(len);
var session_id = "";
for (var i = 0; i < len; i++) {
// Read a byte, convert it to a hex string (0xAB ==> "AB"), and append
// it to session_id.
session_id +=
("0" + Memory.readU8(p.add(i)).toString(16).toUpperCase()).substr(-2);
}
return session_id;
}

Interceptor.attach(addresses["SSL_read"],
{
onEnter: function (args) {
var message = getPortsAndAddresses(SSL_get_fd(args[0]), true);
message["ssl_session_id"] = getSslSessionId(args[0]);
message["function"] = "SSL_read";
message["stack"] = SSLstackread;
this.message = message;
this.buf = args[1];
},
onLeave: function (retval) {
retval |= 0; // Cast retval to 32-bit integer.
if (retval <= 0) {
return;
}
send(this.message, Memory.readByteArray(this.buf, retval));
}
});

Interceptor.attach(addresses["SSL_write"],
{
onEnter: function (args) {
var message = getPortsAndAddresses(SSL_get_fd(args[0]), false);
message["ssl_session_id"] = getSslSessionId(args[0]);
message["function"] = "SSL_write";
message["stack"] = SSLstackwrite;
send(message, Memory.readByteArray(args[1], parseInt(args[2])));
},
onLeave: function (retval) {
}
});

if (Java.available) {
Java.perform(function () {
function storeP12(pri, p7, p12Path, p12Password) {
var X509Certificate = Java.use("java.security.cert.X509Certificate")
var p7X509 = Java.cast(p7, X509Certificate);
var chain = Java.array("java.security.cert.X509Certificate", [p7X509])
var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC");
ks.load(null, null);
ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);
try {
var out = Java.use("java.io.FileOutputStream").$new(p12Path);
ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())
} catch (exp) {
console.log(exp)
}
}
//在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () {
var result = this.getPrivateKey()
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
var message = {};
message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue';
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
var data = Memory.alloc(1);
send(message, Memory.readByteArray(data, 1))
return result;
}
Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () {
var result = this.getCertificateChain()
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue');
var message = {};
message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue';
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
var data = Memory.alloc(1);
send(message, Memory.readByteArray(data, 1))
return result;
}

//SSLpinning helper 帮助定位证书绑定的关键代码
Java.use("java.io.File").$init.overload('java.io.File', 'java.lang.String').implementation = function (file, cert) {
var result = this.$init(file, cert)
var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
if (file.getPath().indexOf("cacert") >= 0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted") >= 0) {
var message = {};
message["function"] = "SSLpinning position locator => " + file.getPath() + " " + cert;
message["stack"] = stack;
var data = Memory.alloc(1);
send(message, Memory.readByteArray(data, 1))
}
return result;
}


Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {
var result = this.socketWrite0(fd, bytearry, offset, byteCount);
var message = {};
message["function"] = "HTTP_send";
message["ssl_session_id"] = "";
message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
message["src_port"] = parseInt(this.socket.value.getLocalPort().toString());
message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
var ptr = Memory.alloc(byteCount);
for (var i = 0; i < byteCount; ++i)
Memory.writeS8(ptr.add(i), bytearry[offset + i]);
send(message, Memory.readByteArray(ptr, byteCount))
return result;
}
Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {
var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
var message = {};
message["function"] = "HTTP_recv";
message["ssl_session_id"] = "";
message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
message["dst_port"] = parseInt(this.socket.value.getLocalPort());
message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
if (result > 0) {
var ptr = Memory.alloc(result);
for (var i = 0; i < result; ++i)
Memory.writeS8(ptr.add(i), bytearry[offset + i]);
send(message, Memory.readByteArray(ptr, result))
}
return result;
}

if (parseFloat(Java.androidVersion) > 8) {
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);
SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
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);
SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
return result;
}
}
else {
Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
var result = this.write(bytearry, int1, int2);
SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
return result;
}
Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
var result = this.read(bytearry, int1, int2);
SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
return result;
}

}
}

)
}

python r0capture.py -U -f com.qiyi.video -p iqiyi.pcap

网络通讯协议分析

Java层

socket抓包

okhttp2.6项目启动tcp服务器

1
tcpdump -i any -s 0 -w /sdcard/1.pcap 抓tcp包

java.net.Socket类构造函数:new Socket(ip,port)->Socket(InetAddress[] addresses, int port, SocketAddress localAddr, boolean stream)

​ ->impl java.net.SocksSocketImpl

建立连接:connect(SocketAddress endpoint)

接收数据:java,net.SocketInputStream.read(byte[])->read(b,0,b.length)->read(b,off,length,impl.getTimeout())->socketRead(fd,b,off,length,timeout)->socketRead0(fd,b,off,length,timeout)(jni函数)

发送数据:java.net.SocketOutputStream.write(byte[])->socketWrite(b,0,b.length)->socketWrite0(fd,b,off,len)->private native void socketWrite0(FileDescriptor fd,byte[] b,int off,int len) throws IOException

frida -UF -l hooktcp.js –no-pause

frida -U -f com.example.okhttp -l hooktcp.js –no-pause -o tcp.log

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();

hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);

}

function printJavaStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, " \n ");
LogPrint("=============================" + name + " Stack strat=======================");
LogPrint(replaceStr);
LogPrint("=============================" + name + " Stack end======================= \n ");
Exception.$dispose();
}
});
}
// 可以打印ascii码
function isprintable(value) {
if (value >= 32 && value <= 126) {
return true;
}
return false;
}

function hooktcp() {
Java.perform(function () {
var SocketClass = Java.use('java.net.Socket');
SocketClass.$init.overload('java.lang.String', 'int').implementation = function (arg0, arg1) {
console.log("[" + Process.getCurrentThreadId() + "]new Socket connection:" + arg0 + ",port:" + arg1);
printJavaStack('tcp connect...')
return this.$init(arg0, arg1);
}
var SocketInputStreamClass = Java.use('java.net.SocketInputStream');
//socketRead0
SocketInputStreamClass.socketRead0.implementation = function (arg0, arg1, arg2, arg3, arg4) {
var size = this.socketRead0(arg0, arg1, arg2, arg3, arg4);
//console.log("[" + Process.getCurrentThreadId() + "]socketRead0:size:" + size + ",content:" + JSON.stringify(arg1));
var bytearray = Java.array('byte', arg1);
var content = '';
for (var i = 0; i < size; i++) {
if (isprintable(bytearray[i])) {
content = content + String.fromCharCode(bytearray[i]);
}
}
var socketimpl = this.impl.value;
var address = socketimpl.address.value;
var port = socketimpl.port.value;

console.log("\naddress:" + address + ",port" + port + "\n" + JSON.stringify(this.socket.value) + "\n[" + Process.getCurrentThreadId() + "]receive:" + content);
printJavaStack('socketRead0')
return size;
}
var SocketOutPutStreamClass = Java.use('java.net.SocketOutputStream');
SocketOutPutStreamClass.socketWrite0.implementation = function (arg0, arg1, arg2, arg3) {
var result = this.socketWrite0(arg0, arg1, arg2, arg3);
//console.log("[" + Process.getCurrentThreadId() + "]socketWrite0:len:" + arg3 + "--content:" + JSON.stringify(arg1));
var bytearray = Java.array('byte', arg1);
var content = '';
for (var i = 0; i < arg3; i++) {

if (isprintable(bytearray[i])) {
content = content + String.fromCharCode(bytearray[i]);
}
}
var socketimpl = this.impl.value;
var address = socketimpl.address.value;
var port = socketimpl.port.value;
console.log("send address:" + address + ",port" + port + "[" + Process.getCurrentThreadId() + "]send:" + content);
console.log("\n" + JSON.stringify(this.socket.value) + "\n[" + Process.getCurrentThreadId() + "]send:" + content);
printJavaStack('socketWrite0')
return result;
}
})
}

function main() {
hooktcp();
}

setImmediate(main)

okhttp2.6项目启动udp服务器

java.net.DatagramSocket->receive

​ -> PlainDatagramSocketImpl

发送数据:PlainDatagramSocketImpl->send IoBridge.sendto(fd, p.getData(), p.getOffset(), p.getLength(), 0, address, port

libcore/io/BlockGuardOs.sendto

1
2
3
4
5
6
7
8
9
10
11
12
294    @Override public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException {
295 BlockGuard.getThreadPolicy().onNetwork();
296 return os.sendto(fd, buffer, flags, inetAddress, port);
297 }
298
299 @Override public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException {
300 // We permit datagrams without hostname lookups.
301 if (inetAddress != null) {
302 BlockGuard.getThreadPolicy().onNetwork();
303 }
304 return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);
305 }

libcore/io/Linux.java.sendto

1
2
3
4
5
6
7
8
9
10
11
12
13
212    public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException {
213 final int bytesSent;
214 final int position = buffer.position();
215
216 if (buffer.isDirect()) {
217 bytesSent = sendtoBytes(fd, buffer, position, buffer.remaining(), flags, inetAddress, port);
218 } else {
219 bytesSent = sendtoBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + position, buffer.remaining(), flags, inetAddress, port);
220 }
221
222 maybeUpdateBufferPosition(buffer, position, bytesSent);
223 return bytesSent;
224 }
1
2
232    private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException;
233 private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException;

接受udp数据

libcore/io/Linux.java.read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
169    public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException {
170 final int bytesRead;
171 final int position = buffer.position();
172
173 if (buffer.isDirect()) {
174 bytesRead = readBytes(fd, buffer, position, buffer.remaining());
175 } else {
176 bytesRead = readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + position, buffer.remaining());
177 }
178
179 maybeUpdateBufferPosition(buffer, position, bytesRead);
180 return bytesRead;
181 }
186 private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException;
207 private native int recvfromBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;

frida -U -f com.example.okhttp -l hookudp.js –no-pause -o udp.log

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
82
83
84
85
86
87
88
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();

hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);

}

function printJavaStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, " \n ");
LogPrint("=============================" + name + " Stack strat=======================");
LogPrint(replaceStr);
LogPrint("=============================" + name + " Stack end======================= \n ");
Exception.$dispose();
}
});
}

function isprintable(value) {
if (value >= 32 && value <= 126) {
return true;
}
return false;
}

function hookudp() {
Java.perform(function () {
var LinuxClass = Java.use('libcore.io.Linux');
// private native int recvfromBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;
LinuxClass.recvfromBytes.implementation = function (arg0, arg1, arg2, arg3, arg4, arg5) {
var size = this.recvfromBytes(arg0, arg1, arg2, arg3, arg4, arg5);
var bytearray = Java.array('byte',arg1);
var content = "";
for(var i=0;i<size;i++) {
content=content+String.fromCharCode(bytearray[i])
}
console.log("address:"+arg5+[" + Process.getCurrentThreadId() + "]recvfromBytes:size:" + size + ",content:" + JSON.stringify(arg1)+"--content:"+content);

printJavaStack('recvfromBytes')
return size;
}
LinuxClass.sendtoBytes.overload('java.io.FileDescriptor','java.lang.Object','int','int','int','java.net.SocketAddress','int').implementation = function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
var size = this.sendtoBytes(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
var bytearray = Java.array('byte',arg1);
var content = "";
for(var i=0;i<size;i++) {
content=content+String.fromCharCode(bytearray[i])
}
console.log("address:"+arg5+"port:"+arg6+[" + Process.getCurrentThreadId() + "]sendtoBytes1:len:" + size + "--content:" + JSON.stringify(arg1)+"--content:"+content);

printJavaStack('sendtoBytes1')
return size;
}
LinuxClass.sendtoBytes.overload('java.io.FileDescriptor','java.lang.Object','int','int','int','java.net.SocketAddress').implementation = function (arg0, arg1, arg2, arg3, arg4, arg5) {
var size = this.sendtoBytes(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
var bytearray = Java.array('byte',arg1);
var content = "";
for(var i=0;i<size;i++) {
content=content+String.fromCharCode(bytearray[i])
}
console.log("address:"+arg5+"[" + Process.getCurrentThreadId() + "]sendtoBytes2:len:" + size + "--content:" + JSON.stringify(arg1)+"--content:"+content);

printJavaStack('sendtoBytes2')
return size;
}
})
}

function main() {
hookudp();
}

setImmediate(main)

ssl抓包

sslSocket->com.android.org.conscrypt.OpenSSLSocketImplWrapper

发送数据:com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream

​ public void write(int oneByte)

​ public void write(byte[] buf, int offset, int byteCount)

1
2
807                NativeCrypto.SSL_write(sslNativePointer, Platform.getFileDescriptor(socket),
808 OpenSSLSocketImpl.this, buf, offset, byteCount, writeTimeoutMilliseconds);
1
2
3
4
1039    public static native void SSL_write(long sslNativePointer,
1040 FileDescriptor fd,
1041 SSLHandshakeCallbacks shc,
1042 byte[] b, int off, int len, int writeTimeoutMillis)

接收数据:com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream

​ public int read()

​ public int read(byte[] buf, int offset, int byteCount)

1
2
3
4
1030    public static native int SSL_read(long sslNativePointer,
1031 FileDescriptor fd,
1032 SSLHandshakeCallbacks shc,
1033 byte[] b, int off, int len, int readTimeoutMillis)

frida -UF -l hook.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
function hookssl() {
Java.perform(function () {
var NativeCrypto = Java.use('com.android.org.conscrypt.NativeCrypto');
NativeCrypto.SSL_read.implementation = function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
var size = this.SSL_read(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
var bytearray = Java.array('byte',arg3);
var content = "";
for(var i=0;i<size;i++) {
content=content+String.fromCharCode(bytearray[i])
}
console.log("\n[" + Process.getCurrentThreadId() +"]ssl receive content:"+content);

printJavaStack('NativeCrypto.read')
return size;
}

NativeCrypto.SSL_write.implementation = function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
var result = this.SSL_write(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
var bytearray = Java.array('byte', arg3);
var content = '';
for (var i = 0; i < arg5; i++) {

if (isprintable(bytearray[i])) {
content = content + String.fromCharCode(bytearray[i]);
}
}
console.log( "\n[" + Process.getCurrentThreadId() + "]send:" + content);
printJavaStack('SSL_write')
return result;
}
})
}
function enumerate() {
Java.perform(function(){
Java.enumerateLoadedClassesSync().forEach(function(classname){
if(classname.indexof("NativeCrypto") >=0) {
console.log(classname)
}
}
})
}
function main() {
enumerate();
hookssl();
}

setImmediate(main)

不过确实对端的ip和端口,根据调用栈向上追溯一层到SSLOutputStream

frida -U -p 12345 -l hookssl2.js –no-pause -o log.txt 多进程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
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
function hookssl2() {
Java.perform(function () {
var SSLInputStreamClass = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream');
SSLInputStreamClass.read.overload('[B','int','int').implementation = function (arg0, arg1, arg2) {
// 获取内部类
var SSLInputStreamObj=this;
var OpenSSLSocketImplobj = this.this.$0.value;
var socketobj = OpenSSLSocketImplobj.socket.value;
var size = this.SSL_read(arg0, arg1, arg2);
var bytearray = Java.array('byte',arg0);
var content = "";
for(var i=0;i<size;i++) {
content=content+String.fromCharCode(bytearray[i])
}
console.log("\naddress:"+"socketobj"+"-----[" + Process.getCurrentThreadId() +"]SSLInputStreamClass receive content:"+content);

printJavaStack('SSLInputStreamClass.read')
return size;
}
var SSLOutputStreamClass = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream');
SSLOutputStreamClass.write.overload('[B','int','int').implementation = function (arg0, arg1, arg2) {
var SSLOutputStreamObj=this;
var OpenSSLSocketImplobj = this.this.$0.value;
var socketobj = OpenSSLSocketImplobj.socket.value;
var result = this.write(arg0, arg1, arg2);
var bytearray = Java.array('byte', arg0);
var content = '';
for (var i = 0; i < arg2; i++) {

if (isprintable(bytearray[i])) {
content = content + String.fromCharCode(bytearray[i]);
}
}
console.log("\naddress:"+"socketobj"+"-----[" + Process.getCurrentThreadId() + "]SSLOutputStreamClass send:" + content);
printJavaStack('SSLOutputStreamClass.write')
return result;
}
})
}
function enumerate() {
Java.perform(function(){
Java.enumerateLoadedClassesSync().forEach(function(classname){
if(classname.indexof("OpenSSLSocketImpl") >=0) {
console.log(classname)
}
}
})
}
function main() {
enumerate();
hookssl();
}

setImmediate(main)

jni层

如果没有使用java层的安卓系统函数将无法抓到,但是wireshark却可以抓到,可以深入jni层抓包

socket抓包

tcp native层

SocketOutputStream_socketWrite0动态注册jni函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
adb pull /system/lib/libopenjdk.so
adb pull /system/lib64/libopenjdk.so libopenjdk64.so

libopenjdk.so - socketRead0
|
NET_Read
|
libc.so - recvfrom
|
系统调用(可以在自己的so中实现recvfrom,然后调用系统调用号)
|
内核

libopenjdk.so - socketWrite0
|
NET_Send
|
libc.so - sendto
|
系统调用
|
内核

将这两个so用ida打开搜索JNI_OnLoad,搜索导出函数中SocketInputStream

image-20221003163522200

image-20221003164042119

1
2
socketRead0->NET_Read->libc.so系统调用ssize_t recvfrom(int fd, void *buf, size_ n, int flags, struct sockaddr *addr, socklen_t *addr_len);
socketWrite0->NET_Send->libc.so系统调用ssize_t sendto(int fd, void *buf, size_ n, int flags,const struct sockaddr *addr, socklen_t *addr_len);

frida -U -f com.example.okhttp -l hooklibc.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
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();

hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);

}

function printNativeStack(context, name) {
//Debug.
var array = Thread.backtrace(context, Backtracer.ACCURATE);
var first = DebugSymbol.fromAddress(array[0]);
if (first.toString().indexOf('libopenjdk.so!NET_Send') < 0) {
var trace = Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
LogPrint("-----------start:" + name + "--------------");
LogPrint(trace);
LogPrint("-----------end:" + name + "--------------");
}

}

function printJavaStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, " \n ");
LogPrint("=============================" + name + " Stack strat=======================");
LogPrint(replaceStr);
LogPrint("=============================" + name + " Stack end======================= \n ");
Exception.$dispose();
}
});
}

function isprintable(value) {
if (value >= 32 && value <= 126) {
return true;
}
return false;
}

function getsocketdetail(fd) {
var result = "";
var type = Socket.type(fd);
if (type != null) {
result = result + "type:" + type;
var peer = Socket.peerAddress(fd);
var local = Socket.localAddress(fd);
result = result + ",address:" + JSON.stringify(peer) + ",local:" + JSON.stringify(local);
} else {
result = "unknown";
}
return result;

}

function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var recvfrom_addr = libcmodule.getExportByName("recvfrom");
var sendto_addr = libcmodule.getExportByName("sendto");
console.log(recvfrom_addr + "---" + sendto_addr);
//ssize_t recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
Interceptor.attach(recvfrom_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

LogPrint("go into libc.so->recvfom");

printNativeStack(this.context, "recvfom");
}, onLeave(retval) {
var size = retval.toInt32();
if (size > 0) {
var result = getsocketdetail(this.arg0.toInt32());
console.log(result + "---libc.so->recvfrom:" + hexdump(this.arg1, {
length: size
}));
}

LogPrint("leave libc.so->recvfom");
}
});
//ssize_t sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
Interceptor.attach(sendto_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
LogPrint("go into libc.so->sendto");
printNativeStack(this.context, "sendto");
}, onLeave(retval) {
var size = ptr(this.arg2).toInt32();
if (size > 0) {
var result = getsocketdetail(this.arg0.toInt32());
console.log(result + "---libc.so->sendto:" + hexdump(this.arg1, {
length: size
}));
}

LogPrint("leave libc.so->sendto");
}
});
}

function main() {
hooklibc();
hooktcp();
}

setImmediate(main);

udp native层

1
2
3
4
5
6
7
libcore.io.Linux.sendtoBytes (jni)
|
libc.so - sendto

libcore.io.Linux.recvfromBytes (jni)
|
libc.so - recvfrom

frida -U -f com.example.okhttp -l hooklibc.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
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds();

hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);

}

function printNativeStack(context, name) {
//Debug.
var array = Thread.backtrace(context, Backtracer.ACCURATE);
var first = DebugSymbol.fromAddress(array[0]);
if (first.toString().indexOf('libopenjdk.so!NET_Send') < 0) {
var trace = Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");// Backtracer.FUZZY
LogPrint("-----------start:" + name + "--------------");
LogPrint(trace);
LogPrint("-----------end:" + name + "--------------");
}

}

function printJavaStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, " \n ");
LogPrint("=============================" + name + " Stack strat=======================");
LogPrint(replaceStr);
LogPrint("=============================" + name + " Stack end======================= \n ");
Exception.$dispose();
}
});
}

function isprintable(value) {
if (value >= 32 && value <= 126) {
return true;
}
return false;
}

function getsocketdetail(fd) {
var result = "";
var type = Socket.type(fd);
if (type != null) {
result = result + "type:" + type;
var peer = Socket.peerAddress(fd);
var local = Socket.localAddress(fd);
result = result + ",address:" + JSON.stringify(peer) + ",local:" + JSON.stringify(local);
} else {
result = "unknown";
}
return result;

}

function getip(ip_ptr) {
var result = ptr(ip_ptr).readU8() + "." + ptr(ip_ptr.add(1)).readU8() + "." + ptr(ip_ptr.add(2)).readU8() + "." + ptr(ip_ptr.add(3)).readU8()
return result;
}

function getudpaddr(addrptr) {
var port_ptr = addrptr.add(2);
var port = ptr(port_ptr).readU8() * 256 + ptr(port_ptr.add(1)).readU8();
var ip_ptr = addrptr.add(4);
var ip_addr = getip(ip_ptr);
return "peer:"+ip_addr+"--port:"+port;
}

function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var recvfrom_addr = libcmodule.getExportByName("recvfrom");
var sendto_addr = libcmodule.getExportByName("sendto");
console.log(recvfrom_addr + "---" + sendto_addr);
//ssize_t recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
Interceptor.attach(recvfrom_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
this.arg3 = args[3];
this.arg4 = args[4];
this.arg5 = args[5];
LogPrint("go into libc.so->recvfom");

printNativeStack(this.context, "recvfom");
}, onLeave(retval) {
var size = retval.toInt32();
if (size > 0) {
var result = getsocketdetail(this.arg0.toInt32());
if (result.indexOf('udp') >= 0) {
/*75struct sockaddr_in {
76 short sin_family;
77 u_short sin_port;
78 struct in_addr sin_addr;
79 char sin_zero[8];
80};*/
var sockaddr_in_ptr = this.arg4;
var sizeofsockaddr_in = this.arg5;

//02 00 22 b8 c0 a8 05 96 00 00 00 00 00 00 00 00
console.log("this is a recvfrom udp!->" + getudpaddr(sockaddr_in_ptr) + "---" + sizeofsockaddr_in);
}
console.log(Process.getCurrentThreadId()+result + "---libc.so->recvfrom:" + hexdump(this.arg1, {
length: size
}));
}

LogPrint("leave libc.so->recvfom");
}
});
//ssize_t sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
Interceptor.attach(sendto_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
this.arg3 = args[3];
this.arg4 = args[4];
this.arg5 = args[5];
LogPrint("go into libc.so->sendto");
printNativeStack(this.context, "sendto");
}, onLeave(retval) {
var size = ptr(this.arg2).toInt32();
if (size > 0) {
var result = getsocketdetail(this.arg0.toInt32());
if (result.indexOf('udp') >= 0) {
/*75struct sockaddr_in {
76 short sin_family;
77 u_short sin_port;
78 struct in_addr sin_addr;
79 char sin_zero[8];
80};*/
var sockaddr_in_ptr = this.arg4;
var sizeofsockaddr_in = this.arg5;

//02 00 22 b8 c0 a8 05 96 00 00 00 00 00 00 00 00
console.log("this is a sendto udp!->" + getudpaddr(sockaddr_in_ptr) + "---" + sizeofsockaddr_in);
}
console.log(Process.getCurrentThreadId()+"---"+result + "---libc.so->sendto:" + hexdump(this.arg1, {
length: size
}));
}

LogPrint("leave libc.so->sendto");
}
});
}

function main() {
hooklibc();
hookudp();
}

setImmediate(main);

frida -U -f com.tencent.news -l hooklibc.js –no-pause -o log.txt

ssl抓包

发送:com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream.write->jni函数

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
7543  static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jlong ssl_address, jobject fdObject,
7544 jobject shc, jbyteArray b, jint offset, jint len,
7545 jint write_timeout_millis) {
com.android.org.conscrypt.NativeCrypto.SSL_write (jni)
|
sslWrite
|
boringssl -> ssl_lib.c -> SSL_write
|
ssl3_write_app_data
|
do_ssl3_write (此时还是明文,之后被加密了)
|
ssl_write_pending
|
ssl_write_buffer_flush
|
dtls_write_buffer_flush/tls_write_buffer_flush
|
BIO_write openssl并没有使用sendto及send发送加密数据,而是使用write,接受没用recvfrom而是read
|
bio_io
|
libc.so - write

com.android.org.conscrypt.NativeCrypto.SSL_read (jni)
|
libc.so - read

frida -U -f com.tencent.news -l hookopenssl.js –no-pause -o log.txt

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
function hooklibc() {
var libcmodule = Process.getModuleByName("libc.so");
var read_addr = libcmodule.getExportByName("read");
var write_addr = libcmodule.getExportByName("write");
console.log(read_addr + "---" + write_addr);
Interceptor.attach(read_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

this.socketinfo = getsocketdetail(this.arg0.toInt32());
LogPrint("go into libc.so->read_addr" + "---" + this.socketinfo);
this.flag = false;
if (this.socketinfo.indexOf("tcp") >= 0) {
this.flag = true;
}
if (this.flag) {
printNativeStack(this.context, Process.getCurrentThreadId() + "read");
}


}, onLeave(retval) {

if (this.flag) {
var size = retval.toInt32();
if (size > 0) {
console.log(Process.getCurrentThreadId() + "---libc.so->read:" + hexdump(this.arg1, {
length: size
}));
}
}


LogPrint("leave libc.so->read");
}
});
Interceptor.attach(write_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

this.socketinfo = getsocketdetail(this.arg0.toInt32());
LogPrint("go into libc.so->write" + "---" + this.socketinfo);
this.flag = false;
if (this.socketinfo.indexOf("tcp") >= 0) {
this.flag = true;
}
if (this.flag) {
printNativeStack(this.context, Process.getCurrentThreadId() + "write");
var size = ptr(this.arg2).toInt32();
if (size > 0) {
console.log(Process.getCurrentThreadId() + "---libc.so->write:" + hexdump(this.arg1, {
length: size
}));
}
}


}, onLeave(retval) {
LogPrint("leave libc.so->write");
}
});
}
// 明文 打印ip port
function hookssl() {
var libcmodule = Process.getModuleByName("libssl.so");
var read_addr = libcmodule.getExportByName("SSL_read");
var write_addr = libcmodule.getExportByName("SSL_write");
var bio_read_addr = libcmodule.getExportByName("BIO_read");
var bio_write_addr = libcmodule.getExportByName("BIO_write");
var SSL_get_rfd_ptr = libsslmodule.getExportByName('SSL_get_rfd');
var SSL_get_rfd = new NativeFunction(SSL_get_rfd_ptr, 'int', ['pointer']);
console.log(read_addr + "---" + write_addr);
Interceptor.attach(read_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

LogPrint("go into libssl.so->SSL_read");
printNativeStack(this.context, Process.getCurrentThreadId() + "SSL_read");

}, onLeave(retval) {

var size = retval.toInt32();
if (size > 0) {
var sockfd = SSL_get_rfd(this.arg0);
var socketdetail = getsocketdetail(sockfd);
console.log(socketdetail+"----"+Process.getCurrentThreadId() + "---libssl.so->SSL_read:" + hexdump(this.arg1, {
length: size
}));
}


LogPrint("leave libc.so->SSL_read");
}
});
Interceptor.attach(write_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

LogPrint("go into libc.so->SSL_write");
printNativeStack(this.context, Process.getCurrentThreadId() + "SSL_write");
var size = ptr(this.arg2).toInt32();
if (size > 0) {
var sockfd = SSL_get_rfd(this.arg0);
var socketdetail = getsocketdetail(sockfd);
console.log(socketdetail+"----"+Process.getCurrentThreadId() + "---libssl.so->SSL_write:" + hexdump(this.arg1, {
length: size
}));
}
}, onLeave(retval) {


LogPrint("libssl.so->SSL_write");
}
});
Interceptor.attach(bio_read_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

LogPrint("go into libssl.so->bio_read");
printNativeStack(this.context, Process.getCurrentThreadId() + "bio_read");

}, onLeave(retval) {

var size = retval.toInt32();
if (size > 0) {
var sockfd = SSL_get_rfd(this.arg0);
var socketdetail = getsocketdetail(sockfd);
console.log(socketdetail+"----"+Process.getCurrentThreadId() + "---libssl.so->bio_read:" + hexdump(this.arg1, {
length: size
}));
}


LogPrint("leave libc.so->bio_read");
}
});
Interceptor.attach(bio_write_addr, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];

LogPrint("go into libc.so->bio_write");
printNativeStack(this.context, Process.getCurrentThreadId() + "bio_write");
var size = ptr(this.arg2).toInt32();
if (size > 0) {
var sockfd = SSL_get_rfd(this.arg0);
var socketdetail = getsocketdetail(sockfd);
console.log(socketdetail+"----"+Process.getCurrentThreadId() + "---libssl.so->bio_write:" + hexdump(this.arg1, {
length: size
}));
}

}, onLeave(retval) {
LogPrint("libssl.so->bio_write");
}
});
}
function main() {
hooklibc();
hookssl();
}

自编译openssl库抓包溯源

hook 自编译 ssl so中的所有导出函数(有符号),so中的符号被抹掉时:通过hook libc.so中的write,read进行堆栈回溯

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
function hookallssl() {
var libsslmodule = Process.getModuleByName("libssl.so");
var SSL_get_rfd_ptr = libsslmodule.getExportByName('SSL_get_rfd'); // 导出函数 获取socket的id
var SSL_get_rfd = new NativeFunction(SSL_get_rfd_ptr, 'int', ['pointer']);
Process.enumerateModules().forEach(function (module) {
module.enumerateExports().forEach(function (symbol) {
var name = symbol.name;
if (name == 'SSL_read') {
LogPrint(JSON.stringify(module) + JSON.stringify(symbol));
}
if (name == 'SSL_write') {
LogPrint(JSON.stringify(module) + JSON.stringify(symbol));
Interceptor.attach(symbol.address, {
onEnter: function (args) {
this.arg0 = args[0];
this.arg1 = args[1];
this.arg2 = args[2];
LogPrint("go into " + Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol));
printNativeStack(this.context, Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol));
var size = ptr(this.arg2).toInt32();
if (size > 0) {
var sockfd = SSL_get_rfd(this.arg0);
var socketdetail = getsocketdetail(sockfd);
console.log(socketdetail + "---" + Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol) + hexdump(this.arg1, {
length: size
}));
}
}, onLeave(retval) {
LogPrint("leave " + Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol));

}
});
}


})
})
}

实战

脱壳

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
// 只适用于存在getDex和getBytes api的android版本,手动dump脱壳指定类的dex
function dumpdex(){
// com.example.socket.MainActivity
// a.a.a.a$a
Java.perform(function(){
var File = Java.use('java.io.File');
var FileOutputStream = Java.use('java.io.FileOutputStream');
Java.enumerateClassLoadersSync().forEach(function(loader){
console.log(loader+"\n");
try{
var aclass = loader.loaderClass("a.a.a.a$a")
console.log(aclass);
var dexobj = aclass.getDex();
console.loader(dexobj);
var dexbytes = dexobj.getBytes();
var dexsavepath = "/data/data/com.example.socket/dump.dex";
var dexfile = File.$new(dexsavepath);
if(!dexfile.exists()){
dexfile.createNewFile();
}
var fileOutputStream = FileOutputStream.$new(dexfile);
fileOutputStream.write(dexbytes);
fileOutputStream.fluse();
fileOutputStream.close();
console.log("save dex success");
} catch(e) {

}
})
})
}

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
// 只适用于存在getDex和getBytes api的android版本,手动dump脱壳指定类的dex
function hook(){
// com.example.socket.MainActivity
// a.a.a.a$a
Java.perform(function(){
var File = Java.use('java.io.File');
var FileOutputStream = Java.use('java.io.FileOutputStream');
Java.enumerateClassLoadersSync().forEach(function(loader){
console.log(loader+"\n");
try{
var aclass = loader.loaderClass("a.a.a.a$a")
Java.classFactory.loader=loader;
var HelloKitty = Java.use('com.example.socket.HelloKitty');
HelloKitty.hello.implementation = function(arg) {
console.log("Hellokitty.hello is called,arg:"+arg);
var result = this.hello(arg);
console.log("Hellokitty.hello called over!,result:"+result);
return result;
}
} catch(e) {

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