前言
参考前面R0capture的代码,具体通过案例来分析抓包的原理和关键源码的追踪。主要包含下面内容:
- Http Request
- Http Response
- Https Request
- Https Response
Hook抓包原理&关键点源码追踪(HTTP request)
demo-实现http请求
创建一个httpsock函数,本质就是简单的java代码,通过socket实现客户端与服务端通信
private void httpsock() { try { final String host = "www.dtasecurity.cn"; final int port = 18080; final String path = "/demo01/getNotice"; Socket socket = new Socket(host,port);
StringBuilder sb = new StringBuilder(); sb.append("GET "+path+" HTTP/1.1\r\n"); sb.append("user-Agent: test\r\n"); sb.append("Host: "+host+"\r\n"); sb.append("\r\n"); Log.d("DTA===>", sb.toString()); OutputStream outputStream = socket.getOutputStream(); outputStream.write(sb.toString().getBytes());
InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len; while( ( len = inputStream.read(buffer,0,buffer.length) ) != -1 ){ Log.d("DTA===>", new String(Arrays.copyOf(buffer,len))); } }catch (Exception e){ e.printStackTrace(); } }
|
分析
根据代码可以看到这块是调用了 outputStream
的 write 方法,实现将数据传送到管道中的。。
跟进到write方法中,可以看到调用byte[]数据,进行数据写入:
这块 outputStream 是一个抽象类,抽象类是不能直接hook的,所以这块需要找到 outputStream 的实现类。。
如何找到它对应的实现类呢?
这块可以在 outputStream.write
处下断点,debug调试找到它的具体实现类为:SocketOutputStream
所以,这块可以查找下 SocketOutputStream 中的write方法,参数类型为byte
调用了 socketWrite 方法,继续往下跟。可以看到真正调用的是try中包裹的socketWrite0方法
跟进socketWrite0 方法,可以看到这块修饰符多了个 native,这是因为函数不是在java层实现的,它的实现在so层。
手写R0capture的Hook代码
这块根据上面的分析逻辑,对 Http Request调用的 SocketOutputStream
中的socketWrite0
函数进行hook:
function main(){ Java.perform(function(){ console.log("Start!") Java.use("java.net.SocketOutputStream").socketWrite0.implementation = function(fd,bytes,off,len){ var localAddress = this.socket.value.getLocalAddress().toString() var remoteAddress = this.socket.value.getRemoteSocketAddress().toString() console.log(localAddress +"====>"+ remoteAddress) hexdump(bytes,off,len) showStacks() this.socketWrite0(fd,bytes,off,len) }
function hexdump(bytearry,offset,length){ var HexDump = Java.use("com.android.internal.util.HexDump") console.log(HexDump.dumpHexString(bytearry,offset,length)) } function showStacks() { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); } }) } setImmediate(main)
|
Http response
分析
同理,对照源码,可以看到这块是调用inputStream.read
方法来获取http的response的。
inputStream.read和 outputStream.write方法是对应关系,这块可以大胆猜测一下,inputStream的实现类是SocketIntputStream
在它的里面,也是有socketRead0方法的,我们这块hook,也是hook的这个方法
hook代码
function main() { Java.perform(function() { Java.use("java.net.SocketInputStream").socketRead0.implementation = function(fd,bytes,off,len,timeout) { console.log("Intercepted socket read"); var localAddress = this.socket.value.getLocalAddress().toString() var remoteAddress = this.socket.value.getRemoteSocketAddress().toString() console.log(remoteAddress +"<===="+ localAddress)
hexdump(bytes,off,len);
showStacks() ;
return this.socketRead0(fd,bytes,off,len,timeout); }; }); }
function hexdump(bytearry,offset,length){ var HexDump = Java.use("com.android.internal.util.HexDump") console.log(HexDump.dumpHexString(bytearry,offset,length)) }
function showStacks() { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }
setImmediate(main);
|
https的Request和Response
编写demo
private void httpsock() { try { final String host = "www.taobao.com"; final int port = 18080; final String path = "/";
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host,port);
StringBuilder sb = new StringBuilder(); sb.append("GET "+path+" HTTP/1.1\r\n"); sb.append("user-Agent: test\r\n"); sb.append("Host: "+host+"\r\n"); sb.append("\r\n"); Log.d("DTA===>", sb.toString()); OutputStream outputStream = socket.getOutputStream(); outputStream.write(sb.toString().getBytes());
InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len; while( ( len = inputStream.read(buffer,0,buffer.length) ) != -1 ){ Log.d("DTA===>", new String(Arrays.copyOf(buffer,len))); } }catch (Exception e){ e.printStackTrace(); } }
|
运行程序,可以看到成功实现了https的socket请求和返回。
request
根据demo可以看到,处理加上了一层SSLSocket,剩下的发送和接收的代码都是没有改动的。
所以,这块request的代码还是: outputStream.write
该位置打断点,查看 outputStream 在此处的实现类为 :ConscryptFileDescriptorSocket$SSLOutputStream,这块的SSLOutputStream为内部类。。。
检索 ConscryptFileDescriptorSocket类,没有找到。猜测可能是系统中的某个类。

这块传入的参数是一个数组。但是没有在SSLOutputStream这个内部类中找到,说明没有对这个方法进行重写,所以,直接查看 OutputStream中的write方法:
这块调用的是三个参数的write方法,也就是SSLOutputStream中的write方法:
调用了 ssl.write(Platform.getFileDescriptor(socket), buf, offset, byteCount,writeTimeoutMilliseconds); 传入了5个参数。
跳转,可以看到实际是调用了SslWraper这个类中的write方法。
void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis) throws IOException { NativeCrypto.SSL_write(ssl, fd, handshakeCallbacks, buf, offset, len, timeoutMillis); }
|
这块最终是到了 NativeCrypto.SSL_write 方法,这个方法是Native类中的了,所以到此就可以了。
总结一下,整体的一个outputStream.write的调用链为:
outputStream.write-->ConscryptFileDescriptorSocket$SSLOutputStream(实现类)-->SSLOutputStream.write(内部类)-->SslWraper.write-->NativeCrypto.SSL_write(Native层)
|
Frida Hook-https request
运行后,发现报错????提示找不到这个 “org.conscrypt.NativeCrypto” 这个类。
猜测,NativeCrypto这个类的包名可能有问题,一般类加载到内存中后,包名可能发生变化。
java文件中NativeCrypto类的包名为 “org.conscrypt”
使用objection,查看内存中加载的类名:
android hooking search classes NativeCrypto
|
修改全类名为 “com.android.org.conscrypt.NativeCrypto”,成功打印出请求的数据和调用堆栈。
function hexdump(bytearry,offset,length){ var HexDump = Java.use("com.android.internal.util.HexDump") console.log(HexDump.dumpHexString(bytearry,offset,length)) }
function showStacks() { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }
function main(){ Java.perform(function(){ Java.use("com.android.org.conscrypt.NativeCrypto").SSL_write.implementation = function(sslNativePointer,fd,shc, bytes,off,len,timeout){ hexdump(bytes,off,len); showStacks(); return this.SSL_write(sslNativePointer,fd,shc, bytes,off,len,timeout); } }) }
setImmediate(main)
|
Frida Hook-https response
同理,根据上面的调用链,可以得到response的最终调用方法为:NativeCrypto.SSL_read
function hexdump(bytearry,offset,length){ var HexDump = Java.use("com.android.internal.util.HexDump") console.log(HexDump.dumpHexString(bytearry,offset,length)) }
function showStacks() { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }
function main(){ Java.perform(function(){ Java.use("com.android.org.conscrypt.NativeCrypto").SSL_read.implementation = function(sslNativePointer,fd,shc, bytes,off,len,timeout){ hexdump(bytes,off,len); showStacks(); return this.SSL_write.call(sslNativePointer,fd,shc, bytes,off,len,timeout); } }) }
setImmediate(main)
|
打印https的local和remote地址
根据之前http打印地址的方法,可以看到是调用了this.socket的方法来打印的
var localAddress = this.socket.value.getLocalAddress().toString() var remoteAddress = this.socket.value.getRemoteSocketAddress().toString()
|
但在 NativeCrypto 类中没有找到socket方法。
继续向前看,可以看到在 “SSLOutputStream.write” 方法里面,使用ssl.write的时候,是有socket的。
只不过,这块将 Platform.getFileDescriptor(socket) 作为了ssl.write的一个参数,这块Platform.getFileDescriptor函数,是一个文件的描述符。可以理解为C语言中的open函数,打开了一个文件,返回了一个文件操作符。。。这块想要拿到它的参数 “socket” ,就是我们的目的。
这块Frida提供了API接口:”Socket”,它里面有定义一些方法,可以获取地址:
Socket.localAddress(handle) Socket.peerAddress(handle)
|
那么问题来了?这块的handle参数到底是什么?
确定参数类型,这块是要输入一个int数字number。。。
function(sslNativePointer,fd,shc, bytes,off,len,timeout)
|
function中传入参数有7个,但其中关于参数 “fd” 的描述是: FileDescriptor fd(文件描述符)
查找 FileDescriptor 类,发现里面是有一个**getInt$()**的系统方法,是可以获取Descriptor的数值的。
直接hook,打印:
整合http+https最终代码如下:
function main() { Java.perform(function() { console.log("Start hooking http/https request/response"); Java.use("java.net.SocketOutputStream").socketWrite0.implementation = function(fd,bytes,off,len){ console.log("<===Hook http request===>"); printHttpAddr(this.socket,true); hexdump(bytes,off,len); showStacks(); return this.socketWrite0(fd,bytes,off,len); } Java.use("java.net.SocketInputStream").socketRead0.implementation = function(fd,bytes,off,len,timeout){ console.log("<===Hook http response===>"); printHttpAddr(this.socket,false); hexdump(bytes,off,len); showStacks(); return this.socketRead0(fd,bytes,off,len,timeout); } Java.use("com.android.org.conscrypt.NativeCrypto").SSL_write.implementation = function(sslNativePointer,fd,shc,bytes,off,len,timeout){ console.log("<===Hook https request===>"); printHttpsAddr(fd,true); hexdump(bytes,off,len); showStacks(); return this.SSL_write(sslNativePointer,fd,shc,bytes,off,len,timeout); } Java.use("com.android.org.conscrypt.NativeCrypto").SSL_read.implementation = function(sslNativePointer,fd,shc, bytes,off,len,timeout){ console.log("<===Hook https response===>"); printHttpsAddr(fd,false); hexdump(bytes,off,len); showStacks(); return this.SSL_read(sslNativePointer,fd,shc, bytes,off,len,timeout); }
function hexdump(bytearry,offset,length){ var HexDump = Java.use("com.android.internal.util.HexDump") console.log(HexDump.dumpHexString(bytearry,offset,length)) }
function showStacks() { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); }
function printHttpAddr(socket,isSend) { var localAddress = socket.value.getLocalAddress().toString(); var remoteAddress = socket.value.getRemoteSocketAddress().toString(); if(isSend) { console.log("Send http request to " + remoteAddress + " from " + localAddress); } else { console.log("Recv http response from " + remoteAddress + " to " + localAddress); } }
function printHttpsAddr(fd,isSend) { var localAddress = Socket.localAddress(fd.getInt$()); var remoteAddress = Socket.peerAddress(fd.getInt$()); if(isSend) { console.log("Send https request to " + remoteAddress.ip + ":" + remoteAddress.port + " from " + localAddress.ip + ":" + localAddress.port); } else { console.log("Recv https response from " + remoteAddress.ip + ":" + remoteAddress.port + " to " + localAddress.ip + ":" + localAddress.port); } } } ) }
setImmediate(main)
|