1.TCP请求 当APP端和服务端基于TCP直接发送请求时,无法抓包。
1.1 示例-服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("192.168.3.136" , 9002 )) server.listen(5 ) while True : client, addr = server.accept() data = client.recv(1024 ) print (data) client.send("ok" .encode('utf-8' )) client.close()
1.2 示例-客户端 示例代码: TCP001
1 2 implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.6.1'
1 <uses-permission android:name ="android.permission.INTERNET" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:id ="@+id/btn1" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="发送请求" /> </LinearLayout > </LinearLayout >
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 package com.nb.tcp001;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.util.Arrays;public class MainActivity extends AppCompatActivity { private Button btn1; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = findViewById(R.id.btn1); btn1.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { doRequest(); } }); } private void doRequest () { new Thread () { @Override public void run () { try { Socket socket = new Socket ("192.168.43.252" , 9001 ); OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello" .getBytes()); InputStream inputStream = socket.getInputStream(); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取相应内容 =>" , new String (Arrays.copyOf(buffer, len))); } socket.close(); } catch (Exception ex) { Log.e("Main" , "网络请求异常" + ex); } } }.start(); } }
1.3 无法抓包
1.4 Hook抓包【java】 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 Java .perform (function ( ) { var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); var HexDump = Java .use ("com.android.internal.util.HexDump" ); var SocketOutputStream = Java .use ('java.net.SocketOutputStream' ); SocketOutputStream .write .overload ('[B' ).implementation = function (arr ) { return this .write (arr); }; var SocketInputStream = Java .use ('java.net.SocketInputStream' ); SocketInputStream .read .overload ('[B' , 'int' , 'int' ).implementation = function (b, off, len ) { var res = this .read (b, off, len); return res; }; });
1.发送请求 请求数据的写入是调用的SocketOutputStream
的write
方法。
1 2 3 OutputStream outputStream = socket.getOutputStream();outputStream.write(sb.toString().getBytes());
所以,底层发送的http请求的数据就是执行 java.net.SocketOutputStream
中的 socketWrite0
方法。
那么,如果我们对这个方法进行Hook,是不是就可以获取到所有http请求的数据了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java .perform (function ( ) { var SocketOutputStream = Java .use ('java.net.SocketOutputStream' ); var HexDump = Java .use ("com.android.internal.util.HexDump" ); var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); SocketOutputStream .socketWrite0 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' ).implementation = function (fd, b, off, len ) { console .log (ByteString .of (b).utf8 (), "\n" ); return this .socketWrite0 (fd, b, off, len); }; });
2.获取响应 1 2 3 4 5 6 7 8 9 10 11 InputStream inputStream = socket.getInputStream();Log.e("inputStream的类 => " , inputStream.getClass().getName()); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取相应内容 =>" , new String (Arrays.copyOf(buffer, len))); }
所以,底层发送的http响应的的数据就是执行 java.net.SocketInputStream
中的 socketRead0
方法。
那么,如果我们对这个方法进行Hook,是不是就可以获取到所有http请求的数据了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java .perform (function ( ) { var SocketInputStream = Java .use ('java.net.SocketInputStream' ); var HexDump = Java .use ("com.android.internal.util.HexDump" ); var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); SocketInputStream .socketRead0 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' , 'int' ).implementation = function (fd, b, off, len, timeout ) { var res = this .socketRead0 (fd, b, off, len, timeout); console .log (ByteString .of (b).utf8 (), "\n" ); return res; }; });
1.5 Hook抓包【so】 上述的请求和写入,都是在java层面进行hook的获取,实际上真正的请求会调用so层的代码,最终将请求发送过去。
1.寻找so文件 寻找socketRead0
和socketWrite0
的hook的脚本:
1 2 3 4 5 6 7 8 Java .perform (function ( ) { const apiResolver = new ApiResolver ('module' ); apiResolver.enumerateMatches ('exports:*!*socket*0*' ).forEach (function (v ) { console .log (v.name ) }); });
1 2 /apex/com.android.runtime/lib64/libopenjdk.so!SocketInputStream_socketRead0 /apex/com.android.runtime/lib64/libopenjdk.so!SocketOutputStream_socketWrite0
2.请求和响应
/apex/com.android.runtime/lib64/libopenjdk.so
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 Java .perform (function ( ) { const NET_Send = Module .getExportByName ('libopenjdk.so' , 'NET_Send' ); const NET_Read = Module .getExportByName ('libopenjdk.so' , 'NET_Read' ); Interceptor .attach (NET_Send, { onEnter (args ) { console .log ('write call' ); console .log (Memory .readByteArray (args[1 ], args[2 ].toInt32 ())); } }); Interceptor .attach (NET_Read, { onEnter (args ) { console .log ('read call' ); this .buf = args[1 ]; }, onLeave : function (retval ) { retval |= 0 ; if (retval <= 0 ) { return ; } console .log (Memory .readByteArray (this .buf , retval)); } }); });
2.Http请求 在app开发时,发送请求会使用okhttp3等第三发模块,但这些模块发送http请求的等调用是基于TCP实现,例如:
1 2 3 4 5 6 7 8 9 10 11 12 OkHttpClient client = new OkHttpClient .Builder().build();Request req = new Request .Builder().url("http://www.chinatelecom.com.cn/corp/01/" ).build();Call call = client.newCall(req);try { Response res = call.execute(); ResponseBody resBody = res.body(); String dataString = resBody.string(); Log.e("=======>" , dataString); } catch (IOException ex) { Log.e("----->" , "请求失败" ); }
基于TCP底层发送的本质:
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 Socket socket = new Socket ("www.chinatelecom.com.cn" , 80 );StringBuilder sb = new StringBuilder ();sb.append("GET /corp/01/ HTTP/1.1\r\n" ); sb.append("host: www.chinatelecom.com.cn\r\n" ); sb.append("user-Agent: test\r\n" ); sb.append("\r\n" ); OutputStream outputStream = socket.getOutputStream();outputStream.write(sb.toString().getBytes()); Log.e("outputStream的类 => " , outputStream.getClass().getName()); InputStream inputStream = socket.getInputStream();Log.e("inputStream的类 => " , inputStream.getClass().getName()); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取相应内容 =>" , new String (Arrays.copyOf(buffer, len))); } socket.close();
2.1 示例-服务端
1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask, jsonifyapp = Flask(__name__) @app.route("/api/info" ) def info (): return jsonify({"code" : 1000 , 'data' : "success" }) if __name__ == '__main__' : app.run(host="0.0.0.0" , port=9003 )
2.2 示例-客户端 示例代码: HTTP002
1 2 3 implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.6.1' implementation "com.squareup.okhttp3:okhttp:3.4.2"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="utf-8" ?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="com.nb.http002" > <uses-permission android:name ="android.permission.INTERNET" /> <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:roundIcon ="@mipmap/ic_launcher_round" android:networkSecurityConfig ="@xml/network_security_config" android:supportsRtl ="true" android:theme ="@style/Theme.HTTP002" > <activity android:name =".MainActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </application > </manifest >
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <network-security-config > <base-config cleartextTrafficPermitted ="true" /> </network-security-config >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:id ="@+id/btn1" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="发送请求1" /> <Button android:id ="@+id/btn2" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="发送请求2" /> </LinearLayout > </LinearLayout >
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 package com.nb.http002;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.util.Arrays;import okhttp3.Call;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;import okhttp3.ResponseBody;public class MainActivity extends AppCompatActivity { private Button btn1; private Button btn2; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = findViewById(R.id.btn1); btn1.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { doRequest1(); } }); btn2 = findViewById(R.id.btn2); btn2.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { doRequest2(); } }); } private void doRequest1 () { new Thread () { @Override public void run () { try { OkHttpClient client = new OkHttpClient .Builder().build(); Request req = new Request .Builder().url("http://192.168.3.136:9003/api/info" ).build(); Call call = client.newCall(req); try { Response res = call.execute(); ResponseBody resBody = res.body(); String dataString = resBody.string(); Log.e("=======>" , dataString); } catch (IOException ex) { Log.e("----->" , "请求失败" + ex.toString()); } } catch (Exception ex) { Log.e("Main" , "网络请求异常" + ex); } } }.start(); } private void doRequest2 () { new Thread () { @Override public void run () { try { Socket socket = new Socket ("192.168.3.136" , 9003 ); StringBuilder sb = new StringBuilder (); sb.append("GET /api/info HTTP/1.1\r\n" ); sb.append("user-Agent: test\r\n" ); sb.append("\r\n" ); OutputStream outputStream = socket.getOutputStream(); outputStream.write(sb.toString().getBytes()); InputStream inputStream = socket.getInputStream(); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取响应内容 =>" , new String (Arrays.copyOf(buffer, len))); } socket.close(); } catch (Exception ex) { Log.e("Main" , "网络请求异常" + ex); } } }.start(); } }
2.2 Hook抓包 Http底层就是基于上述TCP协议的方式。所以,业界基于上述TCP的方式进行抓包。
1.java层 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 Java .perform (function ( ) { var HexDump = Java .use ("com.android.internal.util.HexDump" ); var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); var SocketOutputStream = Java .use ('java.net.SocketOutputStream' ); SocketOutputStream .socketWrite0 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' ).implementation = function (fd, b, off, len ) { console .log ("------------请求--------------" ) console .log (ByteString .of (b).utf8 (), "\n" ); return this .socketWrite0 (fd, b, off, len); }; var SocketInputStream = Java .use ('java.net.SocketInputStream' ); SocketInputStream .socketRead0 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' , 'int' ).implementation = function (fd, b, off, len, timeout ) { var res = this .socketRead0 (fd, b, off, len, timeout); console .log ("------------响应-------------" ) console .log (ByteString .of (b).utf8 (), "\n" ); return res; }; });
2.so层 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 Java .perform (function ( ) { const NET_Send = Module .getExportByName ('libopenjdk.so' , 'NET_Send' ); const NET_Read = Module .getExportByName ('libopenjdk.so' , 'NET_Read' ); Interceptor .attach (NET_Send, { onEnter (args ) { console .log ('write call' ); console .log (Memory .readByteArray (args[1 ], args[2 ].toInt32 ())); } }); Interceptor .attach (NET_Read, { onEnter (args ) { console .log ('read call' ); this .buf = args[1 ]; }, onLeave : function (retval ) { retval |= 0 ; if (retval <= 0 ) { return ; } console .log (Memory .readByteArray (this .buf , retval)); } }); });
3.Https请求 HTTPS请求的底层也是基于TCP发送请求,只不过其中有加入了SSL证书相关。
所以,在基于TCP进行读写时候相关的模块不一样。
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 SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket("api.luffycity.com" , 443 );StringBuilder sb = new StringBuilder ();sb.append("GET /api/v1/course/category/actual/?courseType=actual HTTP/1.1\r\n" ); sb.append("host: api.luffycity.com\r\n" ); sb.append("user-Agent: test\r\n" ); sb.append("\r\n" ); OutputStream outputStream = socket.getOutputStream();outputStream.write(sb.toString().getBytes()); Log.e("outputStream的类 => " , outputStream.getClass().getName()); InputStream inputStream = socket.getInputStream();Log.e("inputStream的类 => " , inputStream.getClass().getName()); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取响应内容 =>" , new String (Arrays.copyOf(buffer, len))); } socket.close();
3.1 示例-客户端 示例:HTTPS003
1 2 3 implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.6.1' implementation "com.squareup.okhttp3:okhttp:3.4.2"
1 <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 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:id ="@+id/btn1" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="发送请求1" /> <Button android:id ="@+id/btn2" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="发送请求2" /> </LinearLayout > </LinearLayout >
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 package com.nb.https003;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.util.Arrays;import javax.net.ssl.SSLSocket;import javax.net.ssl.SSLSocketFactory;import okhttp3.Call;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;import okhttp3.ResponseBody;public class MainActivity extends AppCompatActivity { private Button btn1; private Button btn2; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = findViewById(R.id.btn1); btn1.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { doRequest1(); } }); btn2 = findViewById(R.id.btn2); btn2.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { doRequest2(); } }); } private void doRequest1 () { new Thread () { @Override public void run () { try { OkHttpClient client = new OkHttpClient .Builder().build(); Request req = new Request .Builder().url("https://api.luffycity.com/api/v1/course/category/actual/?courseType=actual" ).build(); Call call = client.newCall(req); try { Response res = call.execute(); ResponseBody resBody = res.body(); String dataString = resBody.string(); Log.e("=======>" , dataString); } catch (IOException ex) { Log.e("----->" , "请求失败" + ex.toString()); } } catch (Exception ex) { Log.e("Main" , "网络请求异常" + ex); } } }.start(); } private void doRequest2 () { new Thread () { @Override public void run () { try { SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket("api.luffycity.com" , 443 ); StringBuilder sb = new StringBuilder (); sb.append("GET /api/v1/course/category/actual/?courseType=actual HTTP/1.1\r\n" ); sb.append("host: api.luffycity.com\r\n" ); sb.append("user-Agent: test\r\n" ); sb.append("\r\n" ); OutputStream outputStream = socket.getOutputStream(); outputStream.write(sb.toString().getBytes()); Log.e("outputStream的类 => " , outputStream.getClass().getName()); InputStream inputStream = socket.getInputStream(); Log.e("inputStream的类 => " , inputStream.getClass().getName()); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取响应内容 =>" , new String (Arrays.copyOf(buffer, len))); } socket.close(); } catch (Exception ex) { Log.e("Main" , "网络请求异常" + ex); } } }.start(); } }
3.2 Hook抓包【java】 1.发送请求 1 2 3 4 OutputStream outputStream = socket.getOutputStream();outputStream.write(sb.toString().getBytes()); Log.e("outputStream的类 => " , outputStream.getClass().getName());
安卓源码地址:
1 http://aospxref.com/android-10.0.0_r47/xref/external/conscrypt/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
http://aospxref.com/android-10.0.0_r47/xref/external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java#1185
1 2 3 4 5 6 7 java.lang.Throwable at com.android.org.conscrypt.NativeCrypto.SSL_write(Native Method) at com.android.org.conscrypt.NativeSsl.write(NativeSsl.java:426) at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write(ConscryptFileDescriptorSocket.java:626) at java.io.OutputStream.write(OutputStream.java:75) at com.nb.ssldemo2.MainActivity$2.run(MainActivity.java:55)
直接hook来进行抓包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Java .perform (function ( ) { var NativeCrypto = Java .use ('com.android.org.conscrypt.NativeCrypto' ); var HexDump = Java .use ("com.android.internal.util.HexDump" ); var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); NativeCrypto .SSL_write .implementation = function (ssl, ssl_holder, fd, shc, b, off, len, timeout ) { console .log (ByteString .of (b).utf8 (), "\n" ); return this .SSL_write (ssl, ssl_holder, fd, shc, b, off, len, timeout); }; });
2.响应获取 1 2 3 4 5 6 7 8 9 10 11 12 InputStream inputStream = socket.getInputStream();Log.e("inputStream的类 => " , inputStream.getClass().getName()); while (true ) { byte [] buffer = new byte [1024 ]; int len = inputStream.read(buffer, 0 , buffer.length); if (len == -1 ) { break ; } Log.e("读取相应内容 =>" , new String (Arrays.copyOf(buffer, len))); }
安卓源码地址:
1 http://aospxref.com/android-10.0.0_r47/xref/external/conscrypt/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
http://aospxref.com/android-10.0.0_r47/xref/external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java#1185
1 2 3 4 5 6 java.lang.Throwable at com.android.org.conscrypt.NativeCrypto.SSL_read(Native Method) at com.android.org.conscrypt.NativeSsl.read(NativeSsl.java:411) at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:549) at com.nb.ssldemo2.MainActivity$2.run(MainActivity.java:65)
直接hook来进行抓包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java.perform(function () { var NativeCrypto = Java.use('com.android.org.conscrypt.NativeCrypto' ); var HexDump = Java.use("com.android.internal.util.HexDump" ); var ByteString = Java.use("com.android.okhttp.okio.ByteString" ); NativeCrypto.SSL_read.implementation = function (ssl, ssl_holder, fd, shc, b, off, len, timeout) { var res = this .SSL_read(ssl, ssl_holder, fd, shc, b, off, len, timeout); console.log(ByteString.of(b).utf8(), "\n" ); return res; }; });
3.3 Hook抓包【so】 上述的请求和写入,都是在java层面进行hook的获取,实际上真正的请求会调用so层的代码,最终将请求发送过去。
1.寻找so文件 一般情况下底层是基于openssl将https请求发送出去,而openssl基于的是libssl.so的文件。
寻找SSL_write
和SSL_read
的hook的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Java .perform (function ( ) { const apiResolver = new ApiResolver ('module' ); apiResolver.enumerateMatches ('exports:*lib*ssl*!SSL_*' ).forEach (function (v ) { if (v.name .indexOf ('SSL_write' ) > 0 ) { console .log (v.name ); } else if (v.name .indexOf ('SSL_read' ) > 0 ) { console .log (v.name ); } }); });
2.请求和响应 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 Java .perform (function ( ) { const SSL_write = Module .getExportByName ('libssl.so' , 'SSL_write' ); const SSL_read = Module .getExportByName ('libssl.so' , 'SSL_read' ); Interceptor .attach (SSL_write, { onEnter (args ) { console .log ('write call' ); console .log (Memory .readByteArray (args[1 ], parseInt (args[2 ]))); } }); Interceptor .attach (SSL_read, { onEnter (args ) { console .log ('read call' ); this .buf = args[1 ]; }, onLeave : function (retval ) { retval |= 0 ; if (retval <= 0 ) { return ; } console .log (Memory .readByteArray (this .buf , retval)); } }); });
4 其他-案例 1.抽屉新热榜 版本:v3.5.8
1.无法抓包 登录页面,无法抓包。
2.NO_PROXY
3.底层抓包 如果是发送的https请求,底层会执行:NativeCrypto.SSL_write
和 NativeCrypto.SSL_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Java .perform (function ( ) { var NativeCrypto = Java .use ('com.android.org.conscrypt.NativeCrypto' ); var HexDump = Java .use ("com.android.internal.util.HexDump" ); var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ); NativeCrypto .SSL_write .implementation = function (ssl, ssl_holder, fd, shc, b, off, len, timeout ) { console .log (ByteString .of (b).utf8 (), "\n" ); return this .SSL_write (ssl, ssl_holder, fd, shc, b, off, len, timeout); }; NativeCrypto .SSL_read .implementation = function (ssl, ssl_holder, fd, shc, b, off, len, timeout ) { var res = this .SSL_read (ssl, ssl_holder, fd, shc, b, off, len, timeout); console .log (ByteString .of (b).utf8 (), "\n" ); return res; }; });
2.B站 2.1 无法抓包 直播间弹幕页面,Charles抓不到包。
2.2 猜测,是否TCP? 在SocketInputStream
中读取内容,发现有数据不断返回,应该是TCP请求返回的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Java .perform (function ( ) { var HexDump = Java .use ("com.android.internal.util.HexDump" ); var SocketInputStream = Java .use ('java.net.SocketInputStream' ); SocketInputStream .socketRead0 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' , 'int' ).implementation = function (fd, b, off, len, timeout ) { var res = this .socketRead0 (fd, b, off, len, timeout); console .log ("------------响应-------------" ) try { console .log (HexDump .dumpHexString (b, off, len), "\n" ) }catch (e) { console .log (e, "\n" ); } return res; }; });
1 2 3 4 5 6 7 8 9 10 11 12 13 java.lang.Throwable at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:119) at java.net.SocketInputStream.read(SocketInputStream.java:176) at java.net.SocketInputStream.read(SocketInputStream.java:144) at okio.l$b.j5(BL:4) at okio.a$b.j5(BL:2) at okio.q.request(BL:3) at okio.q.c4(BL:1) at okio.q.readInt(BL:1) at com.bilibili.bililive.infra.socket.core.codec.reader.a.t(BL:1) 【看这里】 at com.bilibili.bililive.infra.socket.core.codec.reader.a.s(BL:1) at com.bilibili.bililive.infra.socketclient.SocketClient.J(BL:2)
基于Hook响应内容:
1 2 3 4 5 6 7 8 9 10 11 Java .perform (function ( ) { var c = Java .use ("c2.f.j.g.h.b.d.c.c" ); c.$init .implementation = function (v1, v2 ) { console .log ("---->" , v2); var res = this .$init(v1, v2); return res; }; });
2.3 猜测,socket连接 如果是TCP请求,那应该会有socket对象的常见去连接远程服务器。
1 2 3 4 5 6 Socket socket = new Socket ("192.168.43.252" , 9002 );或 Socket socket = new Socket ();InetAddress serverAddress = InetAddress .getByName ("192.168.43.252" );SocketAddress address = new InetSocketAddress (serverAddress, 8080 );socket.connect (address,10000 )
所以,可以尝试编写Hook脚本去获取IP地址和端口等信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Java .perform (function ( ) { var Socket = Java .use ("java.net.Socket" ); Socket .$init .overload ().implementation = function ( ) { console .log ("1.创建socket对象" ); var res = this .$init(); return res; }; Socket .connect .overload ('java.net.SocketAddress' , 'int' ).implementation = function (address, timeout ) { console .log ("2.执行连接" ,address, timeout); console .log (Java .use ("android.util.Log" ).getStackTraceString (Java .use ("java.lang.Throwable" ).$new())); var res = this .connect (address, timeout); return res; }; });
1 2 3 4 5 6 7 8 java.lang.Throwable at java.net.Socket.connect(Native Method) at com.bilibili.bililive.infra.socketclient.g.a.d(BL:4) at com.bilibili.bililive.infra.socketclient.g.a.c(BL:3) at com.bilibili.bililive.infra.socketclient.SocketClient$a.run(BL:6) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:919)
2.4 猜测,发送数据 当连接成功后,手机端会向后端发送请求(告诉后端进入的是那个直播间),然后后端才会源源不断的返回数据。
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 Java .perform (function ( ) { var Socket = Java .use ("java.net.Socket" ); Socket .$init .overload ().implementation = function ( ) { console .log ("1.创建socket对象" ); var res = this .$init(); return res; }; Socket .connect .overload ('java.net.SocketAddress' , 'int' ).implementation = function (address, timeout ) { console .log ("2.执行connect" , address, timeout); var res = this .connect (address, timeout); return res; }; Socket .getOutputStream .implementation = function ( ) { var res = this .getOutputStream (); console .log ("3.写对象" , JSON .stringify (res)) return res; } var SocketOutputStream = Java .use ("java.net.SocketOutputStream" ); SocketOutputStream .write .overload ('[B' , 'int' , 'int' ).implementation = function (v1, v2, v3 ) { console .log ("4.写入数据:" , v1) var res = this .write (v1, v2, v3); return res; } var c = Java .use ("c2.f.j.g.h.b.d.c.c" ); c.$init .implementation = function (v1, v2 ) { console .log ("5.读取数据" , v2); var res = this .$init(v1, v2); return res; }; });
接下来,根据写入数据时的调用栈,找到数据加密的算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 java.lang.Throwable at java.net.SocketOutputStream.write(Native Method) at okio.l$a.A3(BL:5) at okio.a$a.A3(BL:6) at okio.p.flush(BL:3) at com.bilibili.bililive.infra.socketclient.f.b(BL:2) at com.bilibili.bililive.infra.socketclient.f.c(BL:2) 【看这里】 at com.bilibili.bililive.infra.socketclient.SocketClient$b.run(BL:3) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:919)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 上图a函数的调用栈 java.lang.Throwable at com.bilibili.bililive.infra.socketclient.f.a(Native Method) at com.bilibili.bililive.infra.socketclient.SocketClient.O(BL:2) 【看这里】 at com.bilibili.bililive.infra.socket.plugins.a.f(BL:2) at c2.f.j.g.h.b.c.f(BL:4) at com.bilibili.bililive.infra.socketclient.SocketClient.K(BL:4) at com.bilibili.bililive.infra.socketclient.SocketClient.z(BL:8) at com.bilibili.bililive.infra.socketclient.SocketClient.q(BL:1) at com.bilibili.bililive.infra.socketclient.SocketClient$a$a.c(BL:1) at com.bilibili.bililive.infra.socketclient.g.a.c(BL:6) at com.bilibili.bililive.infra.socketclient.SocketClient$a.run(BL:6) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:919)
1.bVar类型 可以Hook找到bVar到底是谁的对象?
1 "<instance: com.bilibili.bililive.infra.socketclient.b, $className: c2.f.j.g.i.d>"
2.fvar.a
3.N相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 java.lang.Throwable at java.net.SocketOutputStream.write(Native Method) at okio.l$a.A3(BL:5) at okio.a$a.A3(BL:6) at okio.p.flush(BL:3) at com.bilibili.bililive.infra.socketclient.f.b(BL:2) at com.bilibili.bililive.infra.socketclient.f.c(BL:2) 【看这里】 at com.bilibili.bililive.infra.socketclient.SocketClient$b.run(BL:3) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:919)
4.谁创建了bVar
5 笔记 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 抓包-底层通信相关 今日概要: - TCP协议,安卓开发 - Http协议,安卓开发 - Https协议,安卓开发 - 案例:抽屉+B站 1.关于TCP和Http协议 2.TCP安卓开发:app和后端 2.1 Python后端 2.2 安卓客户端 2.3 别人家的APP 安卓手机 TCP 后端 基于Charlse无法抓包 安卓手机 http/https 后端(charlse) 2.4 如何抓包:基于Hook进行抓包 发送消息: OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello".getBytes()); - 你的Hook脚本:java.io.OutputStream.write方法【错误】 - 真正Hook脚本:java.net.SocketOutputStream.write方法 接受返回值: InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len = inputStream.read(buffer, 0, buffer.length); - 你的Hook脚本:java.io.InputStream.read方法【错误】 - 真正Hook脚本:java.net.SocketInputStream.read方法 问题:Hook是固定的吗? 问题:获取IP和端口 2.5 深入的Hook脚本(java层) - 真正Hook脚本:java.net.SocketOutputStream.write方法 - socketWrite - socketWrite0 private native void socketWrite0(FileDescriptor fd, byte[] b, int off,int len) throws IOException; - libopenjdk.so - SocketOutputStream_socketWrite0 - NET_Send - 真正Hook脚本:java.net.SocketInputStream.read方法 - socketRead - socketRead0 private native int socketRead0(FileDescriptor fd,byte b[], int off, int len,int timeout) - libopenjdk.so - SocketInputStream_socketRead0 - NET_Read 2.6 深入的Hook脚本(so层) private native void socketWrite0(FileDescriptor fd, byte[] b, int off,int len) throws IOException; private native int socketRead0(FileDescriptor fd,byte b[], int off, int len,int timeout) 1.在那个so文件中 Java.perform(function () { const apiResolver = new ApiResolver('module'); apiResolver.enumerateMatches('exports:*!*socket*0*').forEach(function (v) { console.log(v.name) }); }); /apex/com.android.runtime/lib64/libopenjdk.so!SocketInputStream_socketRead0 /apex/com.android.runtime/lib64/libopenjdk.so!SocketOutputStream_socketWrite0 2.ida打开so 3.HTTP安卓开发:app和后端 3.1 后端 3.2 app端(okhttp3、底层) 3.3 架设没有charlse等工具 4.HTTPS安卓开发:app + 后端路飞学城 4.1 安卓端(HTTPS003) - 基于okhttp3发送https请求 - 基于底层TCP协议,Socket是模块+SSL 4.2 假如charlse不存在了,如何抓包? 4.3 Hook抓包 - 发送 com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write com.android.org.conscrypt.NativeCrypto.SSL_write 【native】 - 获取 com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read com.android.org.conscrypt.NativeCrypto.SSL_read 【 native】 4.4 Hook抓包深入 小结: - TCP、Http、Https协议 - TCP协议请求 -> 固定Hook脚本 - Http和Https抓包 - 基于工具 - 基于源码和Hook【tcp=http固定】【https固定】 5.案例 5.1 抽屉新热榜 v3.5.8 - 登录页面 - 手机代理 & charles抓包 -> 无法抓包 解决方案: - NO_PROXY,可以抓包。【https】 - 底层Hook抓包x 5.3 B站APP v6.24.0 - 手机代理 & charles抓包 - 页面加载、登录 - 直播页面 - 猜想:实时显示 - http或https,无状态短连接。 - TCP协议,不断开连接实时展示。 - 验证猜想:TCP请求(Hook脚本)
__END__