移动端安全 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_socketWrite0SocketInputStream_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 系统调用入口
sendto和recvfrom最终都来自libc.so库,通过系统调用进入内核:
sendto调用号:0x122recvfrom调用号: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. 关键点总结
- 调用链清晰:Java -> JNI -> Native -> 系统调用
- 关键Hook点:
libopenjdk.so中的NET_Send和NET_Readlibc.so中的sendto和recvfrom
- 系统调用号:
sendto: 0x122recvfrom: 0x124
- 架构差异:x86_64模拟器与ARM设备的实现可能有差异
8. 实际应用
通过Hook这些关键点,可以实现:
- 网络通信监控
- 数据包拦截与分析
- 安全漏洞检测
- 恶意行为分析