Socket&Websocket&Protobuf自吐通杀

篇幅有限

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

Socket

所有的应用层都逃不掉底层用Socket来传输,只要掌握了Socket,对上层应用就是降维打击。

新建HttpSocket项目,并在AndroidManifest.xml配置网络权限<uses-permission android:name="android.permission.INTERNET" />

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
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
while (true){
newHttp();
try {
Thread.sleep(10*1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}

private static void newHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try {
String url = "https://www.baidu.com";
URL urlConn = new URL(url);
HttpURLConnection connection = (HttpURLConnection)urlConn.openConnection();
connection.setRequestMethod("GET");
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = connection.getInputStream();
String result = is2String(inputStream);
Log.d("onejane","result===="+result);
}


} catch (Exception e){
e.printStackTrace();
}
}
}).start();
}

private static String is2String(InputStream inputStream) throws IOException {
byte[] buffer = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int len = 0; (len = inputStream.read(buffer)) > 0;) {
baos.write(buffer, 0, len);
}
String result = new String(baos.toByteArray(), "utf-8");
System.out.println(result);

return result ;
}
}

关键类定位

objection -g com.onejane.httpsocket explore

android heap search instances java.net.Socket 查看堆内存中是否有该实例

android hooking search classes Socket 搜索与Socket相关的类

android hooking watch class java.net.Socket 默认hook类的所有方法没有构造函数

android hooking watch class_method java.net.Socket.$init –dump-args –dump-backtrace –dump-return 手动调用hook构造函数

vim ~/.pyenv/versions/3.8.0/lib/python3.8/site-packages/objection/agent.js 输入:9211跳转到9211行,加上.concat([“$init”]),会影响objection的稳定性

hook构造函数

将与Socket相关的类添加前缀android hooking watch class存入socket.txt

objection -g com.onejane.httpsocket explore -c ~/Desktop/socket.txt 批量hook,如果崩掉,需要将崩掉的类从文本中移除

崩掉的类

okhttp底层走的socket

android hooking watch class_method java.net.AbstractPlainSocketImpl.acquireFD --dump-args --dump-backtrace --dump-return

hook AbstractPlainSocketImpl

根据hook的结果拿到java.net.SocketOutputStream.write方法就是socket写入时调用的方法,针对该方法进行hook

android hooking watch class_method java.net.SocketOutputStream.write --dump-args --dump-backtrace --dump-return

hook SocketOutputStream

SocketInputStream.read的hook结果复制到010Editor,搜索1f8b,删除前面所有字符,保存为gzip,解压查看结果就是百度网页结果

socket自吐

实现http与https的socket自吐,修改url地址http://www.baidu.com/为https://www.baidu.com/, 有这两个自吐,可以通杀所有协议层的收发包内容

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)

frida -UF -l hookSocket.js 使用git clone https://github.com/peiniwan/Ganhuo.git 编译源码编译安装GanHuo.apk查看代码家栏,通过010Editor获取转换后的Unicode码,即为抓包结果

hook Socket

Websocket

虚拟机网卡切换为桥接模式:192.168.0.104

server

wget https://github.com/gotify/server/releases/download/v2.0.20/gotify-linux-amd64.zip

unzip gotify-linux-amd64.zip

chmod +x gotify-linux-amd64

./gotify-linux-amd64

client

adb install Gotify.apk

输入server地址http://192.168.0.104 admin/admin

cli

wget -O gotify https://github.com/gotify/cli/releases/download/v1.2.0/gotify-cli-linux-amd64

chmod +x gotify

mv gotify /usr/bin/gotify

gotify init

gotify

gotify push -t “my title” -p 10 “my message” 服务器向手机发送消息

hook_okhttp3_logging

添加十六进制转换,在控制台中以字符串显示

1
2
3
4
5
6
function jhexdump(array) {
var ptr = Memory.alloc(array.length);
for(var i = 0; i < array.length; ++i)
Memory.writeS8(ptr.add(i), array[i]);
console.log(hexdump(ptr, { offset: 0, length: array.length, header: false, ansi: false }));
}

使用console.log(jhexdump(bytearry))替换console.log(“bytearray contents=>”, ByteString.of(bytearry).hex())

frida -UF -l hookSocket.js 使用socket自吐依旧可以通杀抓包

pm list packages|grep -i gotify 获取包名

frida -U -f com.github.gotify -l hookSocket.js –no-pause -o gotify.log

objection -g com.github.gotify explore

android hooking search classes websocket 查找内存中和websocket相关的类很少,可以通过android hooking watch class * ,存入websocket.txt文件批量hook

1
2
3
objection -g com.github.gotify explore -c ~/Desktop/gotify/websocket.txt
plugin wallbreaker objectsearch com.github.gotify.client.model.Message 获取内存中的Message对象
pluginwallbreaker objectdump --fullname 0x2576 获取该对象中字段在内存中的内容

android hooking search classes websocket 发现okhttp3.WebSocket,通过hook_okhttp3_logging脚本进行hook抓包frida -U -f com.github.gotify -l hookOkhttp3.js --no-pause,logcat|grep okhttpGET 查看可以抓到ok3的websocket包

android hooking search classes com.xabber.xmpp.smack 基于xmpp协议聊天软件xabber搜索包名并批量hook

android hooking watch class_method java.lang.String.toString –dump-args -dump-backtrace –dump-return

hookWebSocket

vim hookWebSocket.js 综合基于hook和枚举的方式抓包websocket

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
function hook_okhttp3() {
// 1. frida Hook java层的代码必须包裹在Java.perform中,Java.perform会将Hook Java相关API准备就绪。
Java.perform(function () {

Java.openClassFile("/data/local/tmp/okhttp3logging.dex.dex").load();
// 只修改了这一句,换句话说,只是使用不同的拦截器对象。
var MyInterceptor = Java.use("com.roysue.octolesson2ok3.okhttp3Logging");

var MyInterceptorObj = MyInterceptor.$new();
var Builder = Java.use("okhttp3.OkHttpClient$Builder");
console.log(Builder);
Builder.build.implementation = function () {
this.networkInterceptors().add(MyInterceptorObj);
console.log("hook Build.build successfully !")
return this.build();
};
console.log("hooking_okhttp3...");
});
}



function EnumerateClient(){
Java.perform(function(){
//Java.openClassFile("/data/local/tmp/r0gson.dex").load();
//const gson = Java.use('com.r0ysue.gson.Gson');
var gson2 = Java.use('com.google.gson.Gson');

// 加载包含CurlInterceptor拦截器的DEX
Java.openClassFile("/data/local/tmp/myok2curl.dex").load();
console.log("loading dex successful!")
const curlInterceptor = Java.use("com.moczul.ok2curl.CurlInterceptor");
const loggable = Java.use("com.moczul.ok2curl.logger.Loggable");
var Log = Java.use("android.util.Log");
var TAG = "okhttpGETcurl";
//注册类————一个实现了所需接口的类
var MyLogClass = Java.registerClass({
name: "okhttp3.MyLogClass",
implements: [loggable],
methods: {
log: function (MyMessage) {
Log.v(TAG, MyMessage);
}}
});
const mylog = MyLogClass.$new();
// 得到所需拦截器对象
var curlInter = curlInterceptor.$new(mylog);


// 加载包含logging-interceptor拦截器的DEX
Java.openClassFile("/data/local/tmp/okhttplogging.dex").load();
var MyInterceptor = Java.use("com.r0ysue.learnokhttp.okhttp3Logging");
var MyInterceptorObj = MyInterceptor.$new();

Java.choose("okhttp3.OkHttpClient",{
onMatch:function(instance){
console.log("1. found instance:",instance)
console.log("2. instance.interceptors():",instance.interceptors().$className)
console.log("3. instance._interceptors:",instance._interceptors.value.$className)
//console.log("4. interceptors:",gson2.$new().toJson(instance.interceptors()))
console.log("5. interceptors:",Java.use("java.util.Arrays").toString(instance.interceptors().toArray()))
var newInter = Java.use("java.util.ArrayList").$new();
newInter.addAll(instance.interceptors());
console.log("6. interceptors:",Java.use("java.util.Arrays").toString(newInter.toArray()));
console.log("7. interceptors:",newInter.$className);
newInter.add(MyInterceptorObj);
newInter.add(curlInter);
instance._interceptors.value = newInter;

},onComplete:function(){
console.log("Search complete!")
}
})

})

}

function main(){
hook_okhttp3();
EnumerateClient();
}

setImmediate(main)

OkHttpLogger-Frida

frida -UF -l OkHttpLogger-Frida/okhttp_poker.js 抓包websocket

Protobuf

直播/弹幕协议Protobuf逆向分析,手把手教你使用ProtoBuf,通过gRPC服务在Android上进行网络请求

服务端

192.168.0.102

官方教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
systemctl stop firewalld.service      
systemctl disable firewalld.service
setenforce 0
/etc/selinux/config 修改为SELINUX=disabled
tar -zxvf jdk-8u60-linux-x64.tar.gz
mv jdk1.8.0_60/ /usr/local/
tar -zxf apache-maven-3.6.3-bin.tar.gz -C /usr/local/
vim /etc/profile
JAVA_HOME=/usr/local/jdk1.8.0_60
JRE_HOME=$JAVA_HOME/jre
MAVEN_HOME=/usr/local/apache-maven-3.6.3
PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin::$MAVEN_HOME/bin
CLASSPATH=:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib/dt.jar
export JAVA_HOME JRE_HOME PATH CLASSPATH MAVEN_HOME
source /etc/profile
git clone -b v1.36.1 https://github.com/grpc/grpc-java
git checkout -b v1.32.1 避免无法连接国外仓库gg
cd grpc-java/examples
./gradlew installDist
./build/install/examples/bin/hello-world-server 启动server端口在50051
./build/install/examples/bin/hello-world-client 通过HelloRequest发送Hello World

客户端

192.168.0.104

git clone https://github.com/xuexiangjys/Protobuf-gRPC-Android.git ,其中helloworld.proto通过protoc编译生成HelloReply.java

手动编译,idea中安装GenProtobuf将binary进行encode和decode

wget https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip

unzip protoc-3.13.0-linux-x86_64.zip

as-Tools-Configure GenProtobuf

右键hellooworld.proto点击quick gen protobuf here 实现手动编译,生成的编译代码使用了大量的google的protobuf基础库

protoc

protoc指定编译语言

安装启动Protobuf-gRPC-Android,保证服务端和客户端互相ping通,可以通过nc 192.168.0.102 50052 给服务端发送数据,服务端使用nc -l 50052接收,如果互通消息就可以收到,否则使用NPS将服务端的50051端口服务映射到指定服务器ip的指定端口(需要在服务端安装nps客户端,在nps服务端配置该nps客户端)。

如果没有响应,ps -ef|grep protobuf 获取到进程id后,logcat|grep -i 22916 查看log,退出重进gRPC-普通请求按钮,输入服务端ip,端口及内容发送请求。

grpc普通请求成功

1
2
3
4
5
6
7
adb shell
ps -ef|grep protobuf 获取包名
./data/local/tmp/fs128arm64 启动frida server
pyenv local 3.8.0
objection -g com.xuexiang.protobufdemo explore
android hooking search classes protobuf 将打印出的类前面加上android hooking watch class,存入hook_list.txt
objection -g com.xuexiang.protobufdemo explore -c /root/Desktop/hook_list.txt 实现批量hook,点击发送请求,从请求找寻找调用到的protobuf类相关方法

关注打印出来的几个函数:

com.google.protobuf.WireFormat.makeTag
com.google.protobuf.CodedOutputStream$OutputStreamEncoder.write([B, int, int)
com.google.protobuf.CodedInputStream.readTag()
com.google.protobuf.WireFormat.getTagFieldNumber(int)
com.google.protobuf.Utf8.encode(java.lang.CharSequence, [B, int, int)
com.google.protobuf.CodedInputStream.newInstance([B, int, int, boolean)

1
android hooking watch class_method com.google.protobuf.Utf8.encode --dump-args --dump-backtrace --dump-return

hookencode

同理,makeTag的调用栈也是从用户代码中的writeTo调用而来。

frida -UF -l hookSocket.js -o protobuf.txt 通杀自吐打印出protubuf的包数据

hookSocketProtobuf

1
2
plugin wallbreaker objectsearch com.xuexiang.protobufdemo.HelloReply
plugin wallbreaker objectdump --fullname 0x22ea 内存漫游的源码
文章作者: J
文章链接: http://onejane.github.io/2021/03/14/Socket&Websocket&Protobuf自吐通杀/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏