TCP
thread {
val socket = Socket("xx.xxx.xxx.xx", 8888)
socket.soTimeout = 3000
val os = socket.getOutputStream()
Log.e("Socket", "class name = ${os::class.java.canonicalName}")
os.write(0x00)
}
运行代码,得知 OutputStream 是 SocketOutputStream
。
调用 write 相关方法,跟踪方法堆栈会走到:
private native void socketWrite0(FileDescriptor fd, byte[] b, int off,
int len) throws IOException;
对应 jni 方法为:
/*
* Class: java_net_SocketOutputStream
* Method: socketWrite0
* Signature: (Ljava/io/FileDescriptor;[BII)V
*/
JNIEXPORT void JNICALL
SocketOutputStream_socketWrite0(JNIEnv *env, jobject this,
jobject fdObj,
jbyteArray data,
jint off, jint len)
继续往下跟踪可以得知它调用了 Net_send 方法。
我们还可以使用 IDA 来帮助分析其方法调用链。
先看 SocketOutputStream.c 被编译到了哪个 so 文件里面。
搜索 SocketOutputStream.c,发现在 里面:
filegroup {
name: "libopenjdk_native_srcs",
visibility: [
"//libcore",
],
srcs: [
"SocketOutputStream.c",
],
}
搜索 libopenjdk_native_srcs,发现在 里面:
cc_defaults {
name: "libopenjdk_native_defaults",
srcs: [":libopenjdk_native_srcs"],
搜索 libopenjdk_native_defaults,发现:
cc_library_shared {
name: "libopenjdk",
visibility: [
"//art/build/apex",
],
apex_available: [
"com.android.art",
"com.android.art.debug",
],
defaults: ["libopenjdk_native_defaults"],
shared_libs: [
"libopenjdkjvm",
],
}
// Debug version of libopenjdk. Depends on libopenjdkjvmd.
cc_library_shared {
name: "libopenjdkd",
visibility: [
"//art/build/apex",
],
apex_available: [
"com.android.art.debug",
],
defaults: ["libopenjdk_native_defaults"],
shared_libs: [
"libopenjdkjvmd",
],
}
编译出来的 so 叫 libopenjdk,如果是 debug 编译的后面带个 d。
使用 find 命令找到文件位置(不同的Android版本位置不一样),将 libopenjdk.so 拖出来,放到 IDA 里面看,因为系统代码是不会混淆的,所以非常方便的可以找到符号位置:
.text:000000000002A64C EXPORT SocketOutputStream_socketWrite0
.text:000000000002A64C SocketOutputStream_socketWrite0 ; DATA XREF: LOAD:0000000000001DC0↑o
.text:000000000002A64C ; .data:0000000000037CE0↓o
.text:000000000002A64C
.text:000000000002A64C var_10018= -0x10018
.text:000000000002A64C s= -0x10010
.text:000000000002A64C var_10= -0x10
.text:000000000002A64C var_s0= 0
.text:000000000002A64C var_s10= 0x10
.text:000000000002A64C var_s20= 0x20
.text:000000000002A64C var_s30= 0x30
.text:000000000002A64C var_s40= 0x40
.text:000000000002A64C var_s50= 0x50
.text:000000000002A64C
.text:000000000002A64C ; __unwind {
右键查看调用图:
可以看到,红色圈圈的这条调用链看名字就很可疑,实际上也确实是发送数据的调用链方法。
最后的 sendto 方法是紫色的,我们看看:
.plt:00000000000337F0 ; ssize_t sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
.plt:00000000000337F0 .sendto ; CODE XREF: Java_sun_nio_ch_DatagramChannelImpl_send0+BC↑p
.plt:00000000000337F0 ; Java_sun_nio_ch_DatagramDispatcher_write0+38↑p
.plt:00000000000337F0 ; Java_sun_nio_ch_SocketChannelImpl_sendOutOfBandData+40↑p
.plt:00000000000337F0 ; NET_Send+54↑p
.plt:00000000000337F0 ; NET_SendTo+5C↑p
.plt:00000000000337F0 10 00 00 D0 ADRP X16, #off_356D8@PAGE
.plt:00000000000337F4 11 6E 43 F9 LDR X17, [X16,#off_356D8@PAGEOFF]
.plt:00000000000337F8 10 62 1B 91 ADD X16, X16, #off_356D8@PAGEOFF
.plt:00000000000337FC 20 02 1F D6 BR X17
到了 plt 里面,说明调用的是其他 so 中的方法,其实就是 libc.so 中的方法。
将 libc.so 拖出来,找到 sendto 方法看看,sendto 会调用到 __sendto 里面,还注意到系统调用使用的寄存器是 X8:
.text:00000000000A2F00 ; =============== S U B R O U T I N E =======================================
.text:00000000000A2F00
.text:00000000000A2F00
.text:00000000000A2F00 ; unsigned __int64 __fastcall _sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t)
.text:00000000000A2F00 __sendto ; DATA XREF: .data:off_C6BE0↓o
.text:00000000000A2F00 ; __unwind {
.text:00000000000A2F00 C8 19 80 D2 MOV X8, #0xCE
.text:00000000000A2F04 01 00 00 D4 SVC 0
.text:00000000000A2F08 1F 04 40 B1 CMN X0, #1,LSL#12
.text:00000000000A2F0C 00 94 80 DA CNEG X0, X0, HI
.text:00000000000A2F10 28 67 FF 54 B.HI __set_errno_internal
.text:00000000000A2F10
.text:00000000000A2F14 C0 03 5F D6 RET
.text:00000000000A2F14 ; } // starts at A2F00
可以看到,这里有个系统调用,值是 0xCE,也就是 206。看看 206 表示的意思:
#define __NR_sendto 206
__SYSCALL(__NR_sendto, sys_sendto)
所以,我们甚至可以直接使用系统调用来发送 socket 请求。
与 sendto 对应的 recvfrom 是一样的分析过程,就不赘述了。
UDP
运行代码:
thread {
val buf: ByteArray = "hello android! ".toByteArray()
val sendSocket = DatagramSocket()
val serverAddress = InetAddress.getByName("10.249.50.96")
val outPacket = DatagramPacket(buf, buf.size, serverAddress, 8889)
sendSocket.send(outPacket)
sendSocket.close()
}
断点得到堆栈:
sendto:724, ForwardingOs (libcore.io)
sendto:689, IoBridge (libcore.io)
send:126, PlainDatagramSocketImpl (java.net)
send:721, DatagramSocket (java.net)
invoke:31, SocketActivity$fillButtonLayout$2$1 (com.aprz.mytestdemo.socket)
invoke:26, SocketActivity$fillButtonLayout$2$1 (com.aprz.mytestdemo.socket)
run:30, ThreadsKt$thread$thread$1 (kotlin.concurrent)
最后会走到 libcore.io.Linux 类的 sendtoBytes 方法:
private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException;
private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException;
对应JNI方法为:
static jint Linux_sendtoBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetAddress, jint port) {
ScopedBytesRO bytes(env, javaBytes);
if (bytes.get() == NULL) {
return -1;
}
return NET_IPV4_FALLBACK(env, ssize_t, sendto, javaFd, javaInetAddress, port,
NULL_ADDR_OK, bytes.get() + byteOffset, byteCount, flags);
}
可以看到,它也是调用了 sendto 方法。
所以只需要 hook libc 的 sendto 与 recvfrom 就可以拿到 tcp 与 udp socket 的所有数据。
SSLSocket
之前我们分析了 SSLSocket 的调用堆栈,我们关注的是 SSL_read 与 SSL_write方法。
SSLSocket 也是一个 Socket,当我们 hook 了 sendto 与 recvfrom 方法之后,能拿到 SSLSocket 的数据吗?
实际上是不能的,因为SSL_write 并不是调用的 sendto 方法:
SSL_write ->
ssl->method->write_app_data ->
tls_write_app_data ->
do_tls_write ->
ssl_write_buffer_flush(ssl); ->
tls_write_buffer_flush(ssl); ->
BIO_write(ssl->wbio.get(), buf->data(), buf->size());
static const BIO_METHOD methods_sockp = {
BIO_TYPE_SOCKET, "socket",
sock_write, sock_read,
NULL /* puts */, NULL /* gets, */,
sock_ctrl, NULL /* create */,
sock_free, NULL /* callback_ctrl */,
};
最后到了 socket_write 方法:
static int sock_write(BIO *b, const char *in, int inl) {
bio_clear_socket_error();
#if defined(OPENSSL_WINDOWS)
int ret = send(b->num, in, inl, 0);
#else
int ret = (int)write(b->num, in, inl);
#endif
BIO_clear_retry_flags(b);
if (ret <= 0) {
if (bio_socket_should_retry(ret)) {
BIO_set_retry_write(b);
}
}
return ret;
}
所以,我们可以 hook libc 的 write 与 read 方法,可以得到 SSLSocket 的数据,不过这里的数据是加密了的。