Java层socket抓包与源码分析(下)

xiaoeryu Lv5

创建demo作为分析案例

本章将创建一个简单的udp通信demo,来进行分析并编写Frida Hook脚本

服务端:Python接收器

客户端:App Demo

代码分析

跟上篇一样对接收和发送数据的流程进行hook来获取hook点

调试分析

receive()send()处下断点进行调试

  • 以调试模式运行之后会首先断在receive()

这里继续F7步入

  • 记录下来最后的调用,等下在源码中查找

    java.net.DatagramSocket->receive
        java.net.PlainDatagramSocketImpl->doRecv
    

继续往下执行断在send()

F7步入

  • 记录下来最后的调用

    java.net.DatagramSocket->send
        java.net.PlainDatagramSocketImpl->send
    

源码分析

在源码中查找我们刚才记录到的最后的调用

receive

java.net.DatagramSocket->receive java.net.PlainDatagramSocketImpl->doRecv

143      private void doRecv(DatagramPacket p, int flags) throws IOException {	// 找到了doRecv
144          if (isClosed()) {
145              throw new SocketException("Socket closed");
146          }
147  
148          if (timeout != 0) {
149              IoBridge.poll(fd, POLLIN | POLLERR, timeout);
150          }
151  
152          IoBridge.recvfrom(false, fd, p.getData(), p.getOffset(), p.bufLength, flags, p,
153                  connected);	// 进入IoBridge类找到recvfrom()
154      }
--->>>
606      public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
607          int result;
608          try {
609              InetSocketAddress srcAddress = packet != null ? new InetSocketAddress() : null;
610              result = Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);	// 进入libcore类找到os对象,再找到recvfrom()
611              result = postRecvfrom(isRead, packet, srcAddress, result);
612          } catch (ErrnoException errnoException) {
613              result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
614          }
615          return result;
616      }
--->>>

跟进libcore类

  • 到了这里画个图比较清晰,最后传进来的参数是linux()对象

跟进去Linux对象找recvfrom()

  • 进来之后找到了recvfrom()它最后调用了JNI函数recvfromBytes()

    private native int recvfromBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException;

  • 还有一个意外之喜,这里也有sendtoBytes()方法,大概率就是我们接下来要找的send()的java层最终调用

send

java.net.DatagramSocket->send java.net.PlainDatagramSocketImpl->send

跟之前一样

114      protected void send(DatagramPacket p) throws IOException {
115          if (isClosed()) {
116              throw new SocketException("Socket closed");
117          }
118          if (p.getData() == null || p.getAddress() == null) {
119              throw new NullPointerException("null buffer || null address");
120          }
121  
122          int port = connected ? 0 : p.getPort();
123          InetAddress address = connected ? null : p.getAddress();
124          IoBridge.sendto(fd, p.getData(), p.getOffset(), p.getLength(), 0, address, port);	// 进入IoBridge类找到sendto()
125      }
--->>>
562      public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws IOException {
563          boolean isDatagram = (inetAddress != null);
564          if (!isDatagram && byteCount <= 0) {
565              return 0;
566          }
567          int result;
568          try {
569              result = Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);	// 这里跟刚才一样进入libcore类找到os对象,再找到recvfrom()
570          } catch (ErrnoException errnoException) {
571              result = maybeThrowAfterSendto(isDatagram, errnoException);
572          }
573          return result;
574      }

进来之后我们把调用的图画出来

最后调用的Java层JNI函数是sendtoBytes():这个函数有两个重载,我们调用的是七个参数的重载

private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException;

写hook代码

直接hook最后的这两个JNI函数,并且把其中传递的buf以及IP等信息打印出来

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() + "]socketRead0 > size: " + size + "--content: " + content)

            printJavaStack('recvfromBytes...')
            return size;
        }

        // 发送数据这里使用的是七个参数的重载
        // private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException;
        LinuxClass.sendtoBytes.overload('java.io.FileDescriptor', 'java.lang.Object', 'int', 'int', 'int', 'java.net.InetAddress', '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 + ":" + arg6 + "[" + Process.getCurrentThreadId() + "]sendtoBytes > len: " + size + "--content: " + content)
            
            printJavaStack('sendtoBytes()...')
            return size;
        }
    })
}

打印的结果

把打印结果写入文件方便查看

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

附件:

完整的Frida Hook代码

  • 标题: Java层socket抓包与源码分析(下)
  • 作者: xiaoeryu
  • 创建于 : 2024-06-02 22:29:55
  • 更新于 : 2024-06-06 06:05:14
  • 链接: https://github.com/xiaoeryu/2024/06/02/Java层socket抓包与源码分析(下)/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论