移动端安全 JNI到Native层socket通信hook点追溯
字数 1066 2025-08-23 18:31:18

Android移动端安全:JNI到Native层Socket通信Hook点追溯

1. 背景与概述

本文详细分析Android系统中从Java层到JNI层再到Native层的Socket通信实现,重点追踪socketWrite0和socketRead0函数的调用链路,为安全研究人员提供完整的Hook点分析。

2. JNI层分析

2.1 JNI函数命名规则

Android对JNI函数有固定格式要求:类名_函数名。对于Socket通信,主要关注:

  • SocketOutputStream_socketWrite0
  • SocketInputStream_socketRead0

2.2 关键函数实现

  • SocketOutputStream_socketWrite0使用NET_Send进行数据发送
  • SocketInputStream_socketRead0使用NET_Read进行数据接收

2.3 源码定位

通过查看android.bp构建文件,可以确定这些JNI函数被编译到libopenjdk.so库中:

name: "Libopenjdk native srcs"  // 编译为libopenjdk.so

3. Native层分析

3.1 发送数据调用链

使用IDA分析libopenjdk.so,发送数据的完整调用链为:

j_j_NET_Send -> j_NET_Send -> NET_Send -> j_sendto -> sendto -> __imp_sendto

3.2 接收数据调用链

接收数据的完整调用链为:

j_j_NET_Read -> j_NET_Read -> NET_Read -> recvfrom -> __imp_recvfrom

3.3 系统调用入口

sendtorecvfrom最终都来自libc.so库,通过系统调用进入内核:

  • sendto调用号:0x122
  • recvfrom调用号:0x124

4. 内核交互分析

4.1 sendto系统调用

ARM汇编实现关键点:

EXPORT sendto
sendto:
    MOV R12, SP
    PUSH {R4-R7}
    LDM R12, {R4-R6}
    MOVW R7, #0x122  ; 系统调用号
    SVC 0            ; 触发系统调用
    POP {R4-R7}
    CMN RO, #0x1000
    BXLS LR
    RSB RO, RO, #0
    B j_set_errno_internal

4.2 recvfrom系统调用

ARM汇编实现关键点:

EXPORT recvfrom
recvfrom:
    MOV R12, SP
    PUSH {R4-R7}
    LDM R12, {R4-R6}
    MOV R7, #0x124   ; 系统调用号
    SVC 0            ; 触发系统调用
    POP {R4-R7}
    CMN RO, #0x1000
    BXLS LR
    RSB RO, RO, #0
    B jset_errno_internal

5. Frida Hook实现

5.1 基础Hook脚本

function hooklibc() {
    var libcmodule = Process.getModuleByName("libc.so");
    var recvfrom_addr = libcmodule.getExportByName("recvfrom");
    var sendto_addr = libcmodule.getExportByName("sendto");
    
    // Hook recvfrom
    Interceptor.attach(recvfrom_addr, {
        onEnter: function(args) {
            this.arg0 = args[0]; // fd
            this.arg1 = args[1]; // buf
            this.arg2 = args[2]; // n
            this.arg4 = args[4]; // addr
            LogPrint("go into libc.so->recvfrom");
        },
        onLeave: function(retval) {
            var size = this.arg2.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->recvfrom");
        }
    });
    
    // Hook sendto
    Interceptor.attach(sendto_addr, {
        onEnter: function(args) {
            this.arg0 = args[0]; // fd
            this.arg1 = args[1]; // buf
            this.arg2 = args[2]; // n
            this.arg4 = args[4]; // addr
            LogPrint("go into libc.so->sendto");
        },
        onLeave: function(retval) {
            var size = 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");
        }
    });
}

5.2 辅助功能函数

5.2.1 打印调用堆栈

function printNativeStack(context, name) {
    var trace = Thread.backtrace(context, Backtracer.ACCURATE)
                  .map(DebugSymbol.fromAddress)
                  .join("\n");
    LogPrint("-start:" + name + "-");
    LogPrint(trace);
    LogPrint("-end:" + name + "-");
}

5.2.2 解析Socket信息

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

5.2.3 解析UDP地址

function getip(ip_ptr) {
    return Array.from({length: 4}, (_, i) => ptr(ip_ptr.add(i)).readU8()).join('.');
}

function getUdpAddr(addrptr) {
    var port = addrptr.add(2).readU16();
    var ip_addr = getip(addrptr.add(4));
    return `peer: ${ip_addr} --port: ${port}`;
}

function handleUdp(socketType, sockaddr_in_ptr, sizeofsockaddr_in) {
    var addr_info = getUdpAddr(sockaddr_in_ptr);
    console.log(`this is a ${socketType} udp! -> ${addr_info} --- size of sockaddr_in: ${sizeofsockaddr_in}`);
}

6. 测试Demo

6.1 HTTP客户端实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>

int httpClient() {
    char *host = "x.x.x.x:xxxx";
    int sockfd;
    int len;
    struct sockaddr_in address;
    int result;
    char httpstring[10000];
    char *server_ip = "x.x.x.x";
    
    strcat(httpstring, "GET / HTTP/1.1\r\n");
    strcat(httpstring, "Host: ");
    strcat(httpstring, host);
    strcat(httpstring, "\r\n");
    strcat(httpstring, "Connection: keep-alive\r\n"
           "Cache-Control: max-age=0\r\n"
           "Upgrade-Insecure-Requests: 1\r\n"
           "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36\r\n"
           "DNT: 1\r\n"
           "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n"
           "Accept-Encoding: gzip, deflate, br\r\n"
           "Accept-Language: zh-CN,zh;q=0.9\r\n\r\n");
    
    char buffer[1024];
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(server_ip);
    address.sin_port = htons(80);
    len = sizeof(address);
    
    result = connect(sockfd, (struct sockaddr *)&address, len);
    if (result == -1) {
        perror("oops: client connect error");
        return 1;
    }
    
    printf("befor connect!!");
    send(sockfd, httpstring, strlen(httpstring), 0);
    printf("after write!!\n");
    
    while(recv(sockfd, buffer, sizeof(buffer)-1, 0) > 0) {
        buffer[sizeof(buffer)-1] = '\0';
        printf("%s", buffer);
    }
    
    close(sockfd);
    printf("\n");
    return 0;
}

7. 关键点总结

  1. 调用链清晰:Java -> JNI -> Native -> 系统调用
  2. 关键Hook点
    • libopenjdk.so中的NET_SendNET_Read
    • libc.so中的sendtorecvfrom
  3. 系统调用号
    • sendto: 0x122
    • recvfrom: 0x124
  4. 架构差异:x86_64模拟器与ARM设备的实现可能有差异

8. 实际应用

通过Hook这些关键点,可以实现:

  • 网络通信监控
  • 数据包拦截与分析
  • 安全漏洞检测
  • 恶意行为分析
Android移动端安全:JNI到Native层Socket通信Hook点追溯 1. 背景与概述 本文详细分析Android系统中从Java层到JNI层再到Native层的Socket通信实现,重点追踪socketWrite0和socketRead0函数的调用链路,为安全研究人员提供完整的Hook点分析。 2. JNI层分析 2.1 JNI函数命名规则 Android对JNI函数有固定格式要求: 类名_函数名 。对于Socket通信,主要关注: SocketOutputStream_socketWrite0 SocketInputStream_socketRead0 2.2 关键函数实现 SocketOutputStream_socketWrite0 使用 NET_Send 进行数据发送 SocketInputStream_socketRead0 使用 NET_Read 进行数据接收 2.3 源码定位 通过查看 android.bp 构建文件,可以确定这些JNI函数被编译到 libopenjdk.so 库中: 3. Native层分析 3.1 发送数据调用链 使用IDA分析 libopenjdk.so ,发送数据的完整调用链为: 3.2 接收数据调用链 接收数据的完整调用链为: 3.3 系统调用入口 sendto 和 recvfrom 最终都来自 libc.so 库,通过系统调用进入内核: sendto 调用号: 0x122 recvfrom 调用号: 0x124 4. 内核交互分析 4.1 sendto系统调用 ARM汇编实现关键点: 4.2 recvfrom系统调用 ARM汇编实现关键点: 5. Frida Hook实现 5.1 基础Hook脚本 5.2 辅助功能函数 5.2.1 打印调用堆栈 5.2.2 解析Socket信息 5.2.3 解析UDP地址 6. 测试Demo 6.1 HTTP客户端实现 7. 关键点总结 调用链清晰 :Java -> JNI -> Native -> 系统调用 关键Hook点 : libopenjdk.so 中的 NET_Send 和 NET_Read libc.so 中的 sendto 和 recvfrom 系统调用号 : sendto : 0x122 recvfrom : 0x124 架构差异 :x86_ 64模拟器与ARM设备的实现可能有差异 8. 实际应用 通过Hook这些关键点,可以实现: 网络通信监控 数据包拦截与分析 安全漏洞检测 恶意行为分析