|
本帖最后由 Delina 于 2021-9-16 21:10 编辑
原文链接:android抓包配置与常见hook点小结
android抓包配置与常见hook点小结
1
一、概述
最近要捡回android方面的知识,所以这次记录下配置andriod抓包环境和通过源码查看常见的hook点。有不足的地方还请师傅指出,谢谢。
2
二、常见抓包配置
2.1 Https中间人抓包
Https中间人抓包本质是把一段通信切割成两段通信,其又可细分为客户端校验服务端和服务端校验客户端。
 
2.1.1 https客户端校验服务端抓包
当客户端访问url后,会收到服务端返回的公钥,然后根据手机或浏览器所预置的根证书来判断url与公钥是否匹配,所以这也是我们为什么需要把抓包软件的证书预置到手机或浏览器中的原因。

当电脑打开charles并连接手机后,允许手机设备:

设置charles的http监听端口:
 
手机设置代理填入相应的ip及端口:
 
抓https包需要在浏览器输入chls.pro/ssl 下载charles证书:
 
下载并安装完证书后是保存在用户下的,我们可以通过两种方式将其移动到系统目录下:
使用Magisk的MoveCertificates模块,安装成功后用户的证书默认会导入到系统。
可以通过adb命令把证书放进去,详见:https://www.cnblogs.com/zj420255586/p/14652194.htmlX
 
 
有些app会通过System.getProperty(“http.proxyHost”)检测是否使用代理,我们也可以使用hook修改返回值进行绕过。
2.1.2 https服务端校验客户端抓包
由2.1的第一副图可知,charles分割了通信的过程,与服务器通信的客户端不再是app而是charles,而服务器是可以验证客户端的证书,所以我们需要把app上的证书导入charles中。我们有几种方式去尝试找到客户端的证书:
1、将apk解包后,直接搜索crt、cer、p12相关的证书后缀名。
2、hook住java.security.keystore的load()相关方法,第一个参数为证书的数据流,第二个参数为证书密码:

2.2 vpn抓包
在charles设置socks端口:
 
在手机上安装Postern并新建配置代理和新建配置规则:
:
 
 
VPN抓包的检测与绕过:通过判断Java.net.NetworkInterface.getName()是否等于“tun0”或“ppp0”来判断是否存在VPN。可以hook让其返回“rmnet_data1”来绕过。Android.net.ConnectivityManager.getNetworkCapabilities()也可以检测。
2.3 证书绑定证书绑定机制(SSLpinning)即我们可以在程序中实现不默认信任系统根证书目录中的证书,相当于再加一层校验,常见的有创建配置文件锁定和okhttp锁定的方式。


而我们有几种方式可以进行绕过:
objection自带命令:androidsslpinning disable
hook住com.android.org.conscrypt.Platform的checkServerTrusted方法,并置空函数体
其他情况的补充:https://github.com/WooyunDota/Dr ... Android.Practice.md
3
三、hook抓包
3.1 java层socket抓包3.1.1 Tcp
Java.net.socket类构造函数:

在该构造中调用了四个参数的重载:

 
调用完构造后,传入connect()传入SocketAddress建立连接:
 
成功建立连接后,开始进行收发操作,调用java.net.SocketInputStream.read(byte b[])接收数据:
 
函数中又调用了重载read(byteb[], int off, int length):
 
再调用四个参数的重载read(byteb[], int off, int length, int timeout):
 
经过一系列判断后调用SocketInputStream.socketRead(FileDescriptorfd,byte b[], int off, int len,int timeout):


当接收完数据后,会调用java.net.SocketInputStream.write(byte b[])发送数据:

函数中又调用了重载write(byteb[], int off, int len)

经过一系列判断后调用socketWrite(byteb[], int off, int len):

最终调用了jni的socketWrite0(FileDescriptorfd, byte[] b, int off,int len)进行数据的发送:

所以我们可以对java.net.Socket.init、socketRead0()、socketWrite0()进行相应hook。
3.1.2 Udp
DatagramSocket调用其send(DatagramPacketp)发送DatagramPacket数据报:

函数中调用PlainDatagramSocketImpl.send(DatagramPacket p):
 
PlainDatagramSocketImpl.send(DatagramPacket p)中又调用了IoBrige.sendto(FileDescriptor fd, byte[] bytes, int byteOffset, intbyteCount, int flags, InetAddress inetAddress, int port)
 
在函数中有新建了Linux对象,调用其sendto(FileDescriptorfd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port):
 
 

在sendto(FileDescriptorfd, ByteBuffer buffer, int flags, InetAddress inetAddress, intport)中最终调用了jni的sendtoBytes()方法:

3.2 java层ssl通信抓包
在okhttp中sink用来发送数据,source用来接收数据:
 
 
函数中调用了OpenSSLSocketImpl.getOutputStream()\OpenSSLSocketImpl.getInputStream(),其中新建内部类对象OpenSSLSocketImpl$SSLOutputStream\OpenSSLSocketImpl$ SSLInputStream:
 
 
然后再sink的重载中sink(finalOutputStream out, final Timeout timeout)/ Source source(finalInputStream in, final Timeouttimeout)调用了OpenSSLSocketImpl.write/OpenSSLSocketImpl.read
 
 
 
 
最后在OpenSSLSocketImpl.read/OpenSSLSocketImpl.write中分别调用jni方法NativeCrypto.SSL_read()/NativeCrypto.SSL_write():
 
 
3.3 jni层socket抓包3.3.1 TCP
从上述3.1.1我们知道tcp在java层调用了socketRead0()、socketWrite0()jni函数,找到其对应的的函数位置,其中SocketInputStream_socketRead0()中调用了NET_Read()接收数据存入缓冲区:(若在so直接用tcp通信则会调用libnative-lib.so的httpGet/GetHttpResponseHead)
 
 
在SocketOutputStream_socketWrite0()中调用Net_send()函数对缓冲区的字节进行循环发送:
 
 
SocketOutputStream.c在openjdkstub.mk中所引用,而native.mk又引用了openjdkstub.mk
 
 
所编译的最终模块名为libopenjdk.so
 
函数通过jni_load动态注册,找到socketread0/socketwrite0在so的对应地址:
 
 
通过一系列调用(j_NET_Read-> NET_Read -> recvfrom)最终调用libc的recvfrom:
 
 
通过一系列调用(j_NET_Send-> NET_Send -> sendto)最终调用libc的sendto:
 
 
最后分别经过不同的系统调用号290\292进入内核:
 
 
3.3.2 UDP
从上述3.1.2我们知道ssl通信最终在java层调用了sendtoBytes()、recvfromBytes() 的jni函数,找到其对应的的函数位置,其中Linux_sendtoBytes()中调用了sendto()发送缓冲区的数据:
 
 
 
也就是udp与tcp收发数据最终都是调用recvefrom()\sendto(),只是tcp比udp多了一层封装send()\recv()。
3.4 jni层ssl通信抓包
从上述3.2我们知道ssl通信最终在java层调用jni方法NativeCrypto.SSL_read()/NativeCrypto.SSL_write(),先找到其对应的的函数位置,其中NativeCrypto.SSL_write()中调用了sslWrite()发送缓冲区的数据:
 
 
在sslWrite()中调用了
 
 
跟入SSL_write(SSL*ssl, const void *buf, int num),在函数中SSL结构体根据不同的版本来调用write_app_data()来发送数据:
 
调用了ssl3_write_app_data(SSL*ssl, const uint8_t *buf, int len):
 
接着在其函数中调用了do_ssl3_write(SSL*ssl, int type, const uint8_t *buf, unsignedlen),注意在函数do_ssl3_write是明文密文的分水岭,后续传输的都是密文,然后把处理完的密文缓冲区传入ssl3_write_pending():

在ssl3_write_pending()调用了ssl_write_buffer_flash():

ssl_write_buffer_flesh()主要调用了dtls_write_buffer_flush()和tls_write_buffer_flush(),而这两函数最后还是调用了BIO_write():

 
在BIO_write()中传入相应的函数地址并调用bio_io():
 
在bio_io(BIO*bio, void *buf, int len, size_t method_offset,int callback_flags,size_t *num) 函数中会通过method_offest偏移找到对应的函数地址:
 
最终调用sock_write(BIO*b, const char *in, intinl),有函数可见平台如果非windows后续就调用write(),而非tcp的send():

3.5 自编译ssl库抓包
有的app会使用自编译的openssl为后续与服务器通信,下面的app是保留了符号,没保留可以通过对比调用堆栈:

 
通过Hook对应so中的SSL_write()和SSL_read(),即可得到未经ssl处理的数据:
 
4
四、小结
这次小结了andriod抓包配置和常见的hook点。有不足的地方还请师傅指出,谢谢观看。
参考:
https://www.anquanke.com/post/id/197657#h3-10
https://bbs.pediy.com/
|
|