篇幅有限 完整内容及源码关注公众号:ReverseCode,发送 冲
沙箱 沙箱:对于系统来说,单个APP是没有隐私的,不管是脱壳、还是收发包,都是由系统的API来执行的。HOOK系统的API,就能得到很多APP的关键信息。
APP想要对抗沙箱:
尽可能减少系统API的调用; 尽可能自己实现一定量的算法; 对自己实现的算法进行强混淆; 增加自身算法的复杂度吧:VMP 各大安全公司、杀毒软件公司基本上都会有自己的沙箱,只要病毒/木马在自己的沙箱跑一遍,直接得到执行流、病毒相似性分析,如绑绑安全的安全密钥白盒 ,对于APP也是一样的。
基于hook的沙箱 Youpk Fart 都是沙箱,由于基于系统本身基本无法对抗。
appmon appmon wiki
1 2 3 4 5 6 7 8 9 10 11 12 ./fs128arm64 vim /etc/proxychains4.conf socks5 192.168.0.107 1080 # 电脑主机 ssr选项设置-开启来自局域网的连接 PYTHON_CONFIGURE_OPTS="--disable-ipv6" proxychains4 pyenv install 3.8.2 PYTHON_CONFIGURE_OPTS="--disable-ipv6" proxychains4 pip install frida==12.8.0 PYTHON_CONFIGURE_OPTS="--disable-ipv6" proxychains4 pip install frida-tools==5.3.0 PYTHON_CONFIGURE_OPTS="--disable-ipv6" proxychains4 pip install objection==1.8.4 proxychains wget https://github.com/dpnishant/appmon/archive/refs/heads/master.zip 7z x master.zip cd appmon-master pip install argparse flask termcolor dataset --upgrade --ignore-installed six python appmon.py -a "com.xiaojianbang.app" -p android -s scripts/Android
点击HookTestDemo.apk的算法加密按钮,触发生成./app_dumps/com.xiaojianbang.app.db
访问http://127.0.0.1:5000/ 选择com.xiaojianbang.app
由于显示内容都是[Object Object],修改源码打印hook内容。
1 2 3 4 5 data.value = byteArraytoHexString(digest); 删除 var ByteString = Java.use("com.android.okhttp.okio.ByteString"); 替换 data.value = ByteString.of(digest).hex() frida -UF -l Hash.js
基于源码的沙箱 aosp810r1 解压驱动Vendor image 驱动GPS, Audio, Camera, Gestures, Graphics, DRM, Video, Sensors 解压驱动 解压到aosp810r1中后./extrace-google_devices-sailfish.sh
和./extrace-qcom-sailfish.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # apt update # git config --global user.email "you@example.com" # git config --global user.name "Your Name" # apt install bison tree # dpkg --add-architecture i386 # apt update # apt install libc6:i386 libncurses5:i386 libstdc++6:i386 # apt install libxml2-utils dd if=/dev/zero of=swapfile bs=1024 count=10240000 使用dd创建swapfile作为swap分区空间 mkswap swapfile mkswap创建交换文件 Kali下手动安装openjdk-8-jdk: # wget http://http.kali.org/pool/main/o/openjdk-8/openjdk-8-jdk-headless_8u212-b01-1_amd64.deb # dpkg -i openjdk-8-jdk-headless_8u212-b01-1_amd64.deb # wget http://http.kali.org/pool/main/o/openjdk-8/openjdk-8-jdk_8u212-b01-1_amd64.deb # dpkg -i openjdk-8-jdk_8u212-b01-1_amd64.deb 安装完成后再用: # update-alternatives --config java # update-alternatives --config javac 选择2来切换jdk的版本:见图 最后用version选项来确认版本: # java -version # javac -version source build/envsetup.sh lunch 选择24 aosp_sailfish-userdebug make 编译完成的系统镜像位于当前目录的out/target/product/sailfish/下包括各个img
官方镜像 下载下来后解压将上面编译好的所有img替换到官方镜像解压后的image-sailfish-opm1.171019.011文件夹,并还原创建zip包
1 2 adb reboot bootloader ./flash-all.sh
Android Studio 导入 AOSP 源码 development/tools/idegen/idegen.sh
会在根目录下生成 android.iml 和 android.ipr 这两个文件,这两个文件是 Android Studio 的工程配置文件,这时候其实已经可以直接导入 Android Studio,但会导入所有的源码模块,会很慢,可以进行过滤,除了 frameworks 模块和 packages 模块,其他都给过滤掉,不导入 Android Studio,打开 android.iml 文件,搜下excludeFolder
,在后面加入如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <excludeFolder url="file://$MODULE_DIR$/art" /> <excludeFolder url="file://$MODULE_DIR$/bionic" /> <excludeFolder url="file://$MODULE_DIR$/bootable" /> <excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/cts" /> <excludeFolder url="file://$MODULE_DIR$/dalvik" /> <excludeFolder url="file://$MODULE_DIR$/developers" /> <excludeFolder url="file://$MODULE_DIR$/development" /> <excludeFolder url="file://$MODULE_DIR$/device" /> <excludeFolder url="file://$MODULE_DIR$/docs" /> <excludeFolder url="file://$MODULE_DIR$/external" /> <excludeFolder url="file://$MODULE_DIR$/hardware" /> <excludeFolder url="file://$MODULE_DIR$/kernel" /> <excludeFolder url="file://$MODULE_DIR$/libcore" /> <excludeFolder url="file://$MODULE_DIR$/libnativehelper" /> <excludeFolder url="file://$MODULE_DIR$/out" /> <excludeFolder url="file://$MODULE_DIR$/pdk" /> <excludeFolder url="file://$MODULE_DIR$/platform_testing" /> <excludeFolder url="file://$MODULE_DIR$/prebuilts" /> <excludeFolder url="file://$MODULE_DIR$/sdk" /> <excludeFolder url="file://$MODULE_DIR$/system" /> <excludeFolder url="file://$MODULE_DIR$/test" /> <excludeFolder url="file://$MODULE_DIR$/toolchain" /> <excludeFolder url="file://$MODULE_DIR$/tools" /> <excludeFolder url="file://$MODULE_DIR$/.repo" />
发现 Android Studio 不停 scanning files to index,我的强迫症又犯了,解决:
invalidate and restart 不起作用; 右击项目 –> Open module setting –> Modules –> 找到 gen 文件夹 –> 右键选择 Resources,终于告别烦人的 scanning files to index。 修改MessageDigest.java
基于android6.0.1 Nexus 6P ROM ,系统底包
先刷官方原版底包,老版本使用fastboot6.0放到kali的/root/Android/Sdk/plateform-tools,flash-all.sh
手机先刷入fastbboot flash recovery twrp 下载rom解压,adb push ROM/ /sdcard/TWRP/BACKUPS 进入twrp,从备份中恢复Restore,重启手机,然后修改权限 chmod 777 /data/local/tmp/monitor_package 安装你需要监控的apk(系统自动把最后一次安装的apk添加进去监控的列表 /data/local/tmp/monitor_package),只能同时监控一个adb install HookTestDemo.apk
/data/data/package_name/下面生成APK调用的算法,只有三种(数据均为JSON编码,字段为BASE64编码)/data/data/com.xiaojianbang.app
参考crypto_filter_aosp文件夹源码添加到aosp810r1的源码中,将MyUtil.java,ContextHolder.java,AndroidBase64.java,Cipher.java放到aosp810r1/libcore/ojluni/src/main/java/javax/crypto
将参考20200212/MessageDigest.java代码实现到aosp810r1的MessageDigest.java。同理,修改Mac.java
openjdk_java_files.mk添加新增的需要编译的类
1 2 3 ojluni/src/main/java/javax/crypto/Mac.java \ ojluni/src/main/java/javax/crypto/ContextHolder.java \ ojluni/src/main/java/javax/crypto/MyUtil.java \
1 2 3 source build/envsetup.sh lunch aosp_sailfish-user make 如报错make update-api
编译完成后将编译好的img压缩成image-sailfish-opm1.17019.011.zip放到官方系统底包,./flash-all.bat
刷机
AOSP网络库自吐 适用于沙箱的原则:我们要可以在安卓源码中找到其实现、彻底的修改其实现。
App开发实力越强,App自己实现的内容越多,对系统的依赖程度越低,沙箱的作用就越小。→ 沙箱只能帮助定位到关键的点,如何把内容解开还是分析自己实现的部分。
为了能抓到包,无数安全研究人员使出浑身解数,我们可以按照OSI七层模型或TCP/IP四层模型。
我们在谈论MAC地址/ARP的时候,我们聊的就是链路层; 我们在谈论IP地址/路由器的时候,我们聊的就是网络层; 我们在谈论连接某个端口的时候,我们聊的就是传输层; 我们在谈论发送数据的内容的时候,我们聊的就是应用层;
应用层/Application:基于中间人的HTTP(S)抓包
该方法继承于网页端的抓包,只不过对抗性全面强化;在设计网站时无法控制客户端,但是App确是可以被厂商全面控制的; 在客户端校验服务器证书的情况下,需要将抓包软件(推荐Charles)的证书置于手机根证书目录下,推荐Magisk插件Move Certificates ; 在服务器验证客户端证书的情况下,还需要在App中dump出证书导入到Charles 中,这就涉及到证书密码和证书的解密; App使用特定API,绕过WIFI代理进行通信→ 使用VPN将所有流量导入到Charles → App还会检测VPN ,发现即断网 → 需要hook过VPN检测;
哪些是可以改的:(沙箱在辅助中间人抓包的过程中发挥的作用)
App使用SSL pinning,只信任自己的证书 → 从数十种框架 中找到hook点 并绕过 → App进行了代码混淆 → 反混淆并hook绕过,而反混淆总是让人倒吸一口凉气。。。
由于厂商可以全面控制客户端,因此可以使用小众协议,比如WebSocket、Protobuf ,甚至自己写协议,比如腾讯的JceStruct ,此时除了自己分析协议字段别无他法 传输层/Transport:App使用纯Socket通信
比如某应用 的数据采用点对点纯Socket的tcp通信,此时只有dump其通信流量,分析其raw data,结合源码分析字段构成; 某厂商 开创性地提出了自建代理长连通道的网络加速方案,App中绝大部分的请求通过CIP通道中的TCP子通道与长连服务器通信,长连服务器将收到的请求代理转发到业务服务器,对于业务来讲大大提高了效率,但是对于逆向来说却加大了抓包的难度。
也幸亏其SDK中包含了降级方案,可以hook某些关键函数实现降级到HTTP,给了安全研究员一口饭吃。更有大厂已经在通讯标准演进的路线上大步快跑,在目前HTTP/2都没有普及的情况下,受益于相比于网页端而言、App客户端全面可控的优势,提前迈入HTTP/3时代 ,在性能优化的KPI上一骑绝尘而去,从内核、算法、传输层网络库和服务端全部自研。
面对连抓包工具都没有提供支持的kQUIC,逆向分析者只能说欲哭无泪。同样还是幸亏SDK中包含了plan B
降级方案,可以通过hook来进行降级,安全研究员续命一秒钟。
网络层/Network:一般而言鲜有App可以更改设备的IP地址
科学上网软件、VPN可以改手机的路由表,因此可以用来抓包; 可以自建路由器进行抓包,对手机完全无侵入、无感知,彻底搞定抓不到包!
缺点是加密内容也无法还原,可以dump流量,却无法解密内容;在手机端连标准的SSL也解不开。也可以在手机上安装使用Kali Nethunter
,在手机上直接跑Wireshark
,接在4G流量卡上进行抓包,这种方式甚至可以抓到手机的流量卡的网卡包,应该是目前已知的唯一抓流量卡的方法。
应用层抓包通杀脚本 App在开发过程中,以App自己的权限,可以用代码实现到的最底层为传输层,也就用Socket接口,进行纯二进制的收发包,此处包括Java层和Native层。 除了少数开发实力雄厚甚至过剩的大厂,掌握着纯二进制收发包的传输层创新、或者自定义协议的技术之外,占绝对数量绝大多数的App厂商采用的还是传统的HTTP/SSL方案。 而且占绝对数量中绝大多数的App,其实现HTTP/SSL的方案也是非常的直白,那就是调用系统的API,或者调用更加易用的网络框架,比如访问网站的Okhttp框架 ,播放视频的Exoplayer ,异步平滑图片滚动加载框架Glide ,对于非网络库或协议等底层开发者来说,这些才应当是普罗大众安卓应用开发者的日常。
所以我们在对Java
层Socket
接口进行trace
之后打调用栈,即可清晰地得出从肉眼可见的视频、到被封装成HTTP包、再到进入SSL进行加解密,再通过Socket
与服务器进行通信的完整过程。
只要开发者使用了应用层框架,即无法避免的使用了系统的Socket进行了收发,如果是HTTP
则直接走了Socket
,没有加解密、直接是明文,将内容dump下来即可;如果走了HTTPS
,那么HTTP包还要“裹上”一层SSL,通过SSL的接口进行收发,SSL
则将加密后和解密前的数据走Socket
与服务器进行通信,明文数据只有SSL
库自己知道。
因此想要得到SSL加密前和解密后的HTTP数据的话,就要对SSL库有深入的研究,而像这种大型的、历史悠久的基础库,研究它的人是非常多的;比如谷歌就有研究员对OpenSSL
的收发包接口进行了深入的研究,并对其收发包等接口使用frida进行hook,提取明文HTTP数据,最终的成品为ssl_logger项目 ;因为这种库一般作为互联网世界架构的基础设施,所以其应用非常广泛,这也是为何当其暴漏出“心脏滴血” 漏洞时,几乎影响到所有互联网设备的原因,不管是Linux
、Macos/iOS
、还是安卓,使用的都是OpenSSL
,刚刚我们trace
到的SSLInputStream.read
函数,充其量只是OpenSSL
库在Java
层的一个包装器罢了。
而又有来自阿里的巨佬,在使用的过程中,进一步优化了该项目的JS脚本,修复了在新版frida上的语法错误,并在原项目只支持Linux
和macOS
的基础上,增加了对iOS
和Android
的支持,最终的成品就是frida_ssl_logger项目 。
该项目的完成度已经非常高,其核心原理就是对SSL_read
和SSL_write
进行hook
,得到其收发包的明文数据。
1 2 [Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android [Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]]
并将明文数据使用RPC
传输到电脑上,使用hexdump
在python
的控制台进行输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 if verbose: src_addr = socket.inet_ntop(socket.AF_INET, struct.pack(">I", p["src_addr"])) dst_addr = socket.inet_ntop(socket.AF_INET, struct.pack(">I", p["dst_addr"])) print("SSL Session: " + p["ssl_session_id"]) print("[%s] %s:%d --> %s:%d" % ( p["function"], src_addr, p["src_port"], dst_addr, p["dst_port"])) hexdump.hexdump(data)
或者保存至pcap
文件,以供后续进一步分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port, dst_addr, dst_port, data): """Writes the captured data to a pcap file. Args: pcap_file: The opened pcap file. ssl_session_id: The SSL session ID for the communication. function: The function that was intercepted ("SSL_read" or "SSL_write"). src_addr: The source address of the logged packet. src_port: The source port of the logged packet. dst_addr: The destination address of the logged packet. dst_port: The destination port of the logged packet. data: The decrypted packet data. """ t = time.time() if ssl_session_id not in ssl_sessions: ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF), random.randint(0, 0xFFFFFFFF)) client_sent, server_sent = ssl_sessions[ssl_session_id] if function == "SSL_read": seq, ack = (server_sent, client_sent) else: seq, ack = (client_sent, server_sent) for writes in ( # PCAP record (packet) header ("=I", int(t)), # Timestamp seconds ("=I", int((t * 1000000) % 1000000)), # Timestamp microseconds ("=I", 40 + len(data)), # Number of octets saved ("=i", 40 + len(data)), # Actual length of packet # IPv4 header (">B", 0x45), # Version and Header Length (">B", 0), # Type of Service (">H", 40 + len(data)), # Total Length (">H", 0), # Identification (">H", 0x4000), # Flags and Fragment Offset (">B", 0xFF), # Time to Live (">B", 6), # Protocol (">H", 0), # Header Checksum (">I", src_addr), # Source Address (">I", dst_addr), # Destination Address # TCP header (">H", src_port), # Source Port (">H", dst_port), # Destination Port (">I", seq), # Sequence Number (">I", ack), # Acknowledgment Number (">H", 0x5018), # Header Length and Flags (">H", 0xFFFF), # Window Size (">H", 0), # Checksum (">H", 0)): # Urgent Pointer pcap_file.write(struct.pack(writes[0], writes[1])) pcap_file.write(data) if function == "SSL_read": server_sent += len(data) else: client_sent += len(data) ssl_sessions[ssl_session_id] = (client_sent, server_sent)
由于完成度已经相当高了,在构建安卓应用层抓包通杀脚本时,应当尽可能复用其已经实现好的“基础设施”,只要为其再补上明文数据即可,而这明文数据从哪里来?根据多轮trace
可以得知,明文数据的收发包接口,正是由java.net.SocketOutputStream.socketWrite0
和java.net.SocketInputStream.socketRead0
这两个API
负责的,当然其实二者还有很多上层调用的接口,在选择分析的接口时,应尽量选择离native
层更近的、并且在更多安卓版本上适用的,比如这两个API在安卓7、8、9、10上是通用和不变的,以降低工作量。
最后的任务就是与SSL_read
和SSL_write
一样,根据收发的函数、找到收发的IP地址和端口,而正好两个API均有socket
的实例域,提供了收发包的IP地址和端口信息。
最终就是取出这些信息,构造与SSL
一样发给电脑即可,需要注意的是Java
的[B
需要手动转化成JavaScript
的ByteArray
还是略微复杂的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 if (Java.available) { Java.perform(function () { Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) { var result = this.socketWrite0(fd, bytearry, offset, byteCount); var message = {}; message["function"] = "HTTP_send"; message["ssl_session_id"] = ""; message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop())); message["src_port"] = parseInt(this.socket.value.getLocalPort().toString()); message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop())); message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop()); var ptr = Memory.alloc(byteCount); for (var i = 0; i < byteCount; ++i) Memory.writeS8(ptr.add(i), bytearry[offset + i]); send(message, Memory.readByteArray(ptr, byteCount)) return result; } Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) { var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout); var message = {}; message["function"] = "HTTP_recv"; message["ssl_session_id"] = ""; message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop())); message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop()); message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop())); message["dst_port"] = parseInt(this.socket.value.getLocalPort()); if (result > 0) { var ptr = Memory.alloc(result); for (var i = 0; i < result; ++i) Memory.writeS8(ptr.add(i), bytearry[offset + i]); send(message, Memory.readByteArray(ptr, result)) } return result; } }) }
One more thing,虽然直接调用native层Socket的应用框架几乎没有;但是Javs层的Socket API是可以进一步下沉到C层的Socket,以支援so文件的socket抓包。以java.net.SocketOutputStream.socketWrite0
举例,其native层的实现为JNIEXPORT void JNICALL 55SocketOutputStream_socketWrite0(JNIEnv *env, jobject this,jobject fdObj,jbyteArray data,jint off, jint len)
(地址 ),其核心为一句话int n = NET_Send(fd, bufP + loff, llen, 0);
,进一步追踪NET_Send
可以在linux_close.cpp
文件中找到其实现(地址 ),本质上也是libc
的send、sendto、recv、recvfrom
这些,因此可以直接hook这些接口,捕获该进程的所有通信流量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 int NET_Read(int s, void* buf, size_t len) { BLOCKING_IO_RETURN_INT( s, recv(s, buf, len, 0) ); } int NET_ReadV(int s, const struct iovec * vector, int count) { BLOCKING_IO_RETURN_INT( s, readv(s, vector, count) ); } int NET_RecvFrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen) { socklen_t socklen = *fromlen; BLOCKING_IO_RETURN_INT( s, recvfrom(s, buf, len, flags, from, &socklen) ); *fromlen = socklen; } int NET_Send(int s, void *msg, int len, unsigned int flags) { BLOCKING_IO_RETURN_INT( s, send(s, msg, len, flags) ); } int NET_WriteV(int s, const struct iovec * vector, int count) { BLOCKING_IO_RETURN_INT( s, writev(s, vector, count) ); } int NET_SendTo(int s, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen) { BLOCKING_IO_RETURN_INT( s, sendto(s, msg, len, flags, to, tolen) ); } int NET_Accept(int s, struct sockaddr *addr, int *addrlen) { socklen_t socklen = *addrlen; BLOCKING_IO_RETURN_INT( s, accept(s, addr, &socklen) ); *addrlen = socklen; } int NET_Connect(int s, struct sockaddr *addr, int addrlen) { BLOCKING_IO_RETURN_INT( s, connect(s, addr, addrlen) ); } #ifndef USE_SELECT int NET_Poll(struct pollfd *ufds, unsigned int nfds, int timeout) { BLOCKING_IO_RETURN_INT( ufds[0].fd, poll(ufds, nfds, timeout) ); } #else int NET_Select(int s, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { BLOCKING_IO_RETURN_INT( s-1, select(s, readfds, writefds, exceptfds, timeout) ); }
只是如果hook native层的这些接口的话,会混进openssl/boringssl的经过加密的流量,届时会比较难以区分,所以其实duck不必下降到native层,Java层的通信足以覆盖99%以上的场景(这个百分比是我估计的)。
最终也就是现在的效果:r0capture:安卓应用层抓包通杀脚本,地址:https://github.com/r0ysue/r0capture
仅限安卓平台,测试安卓7、8、9、10 可用 ; 无视所有证书校验或绑定,不用考虑任何证书的事情; 通杀TCP/IP四层模型中的应用层中的全部协议; 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本; 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等; 用法
1 $ python3 r0capture.py -U -f com.qiyi.video
Attach 模式,抓包内容保存成pcap文件供后续分析: 1 $ python3 r0capture.py -U com.qiyi.video -p iqiyi.pcap
建议使用Attach模式,从感兴趣的地方开始抓包,并且保存成pcap文件,供后续使用Wireshark进行分析。
PS:用来抓注册包,效果尤佳。
To-do:
此处还是有部分开发实力过强的大厂或框架,采用的是自身的SSL框架,比如WebView、小程序 或Flutter ,这部分目前暂未支持。当然这部分App也是少数。 暂不支持HTTP/2、或HTTP/3,该部分API在安卓系统上暂未普及或布署,为App自带,无法进行通用hook。 各种模拟器架构、实现、环境较为复杂,建议珍爱生命、使用真机。 暂未添加多进程支持,比如:service或:push等子进程,可以使用Frida的Child-gating来支持一下。 支持多进程之后要考虑pcap
文件的写入锁问题,可以用frida-tool
的Reactor
线程锁 来支持一下。 TCP/IP中可以实现的部分:
SSL 安装HttpSocket
1 2 3 objection -g com.onejane.httpsocket explore android hooking search ssl 将所有打印出的类放到sslandroid8.txt中,前面批量加上android hooking watch class objection -g com.onejane.httpsocket explore -c sslandroid8.txt 批量hook,报错ClassLoader就删除包括ClassLoader类
android hooking watch class com.android.org.conscrypt.OpenSSLBIOInputStream android hooking watch class com.android.org.conscrypt.OpenSSLCipher android hooking watch class com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER android hooking watch class com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER$AES android hooking watch class com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER$AES$CBC android hooking watch class com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER$AES$CBC$PKCS5Padding android hooking watch class com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER$AES_BASE android hooking watch class com.android.org.conscrypt.OpenSSLContextImpl
…
在安卓8上结果
(agent) [lrxbzy1b2ea] Called javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier() (agent) [lrxbzy1b2ea] Called javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory() (agent) [lrxbzy1b2ea] Called javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory() (agent) [nrgnn66lv3e] Called com.android.org.conscrypt.OpenSSLSocketImpl.isClosed() (agent) [nrgnn66lv3e] Called com.android.org.conscrypt.OpenSSLSocketImpl.isInputShutdown() (agent) [nrgnn66lv3e] Called com.android.org.conscrypt.OpenSSLSocketImpl.isOutputShutdown() (agent) [42lol483nwl] Called com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write([B, int, int) (agent) [nrgnn66lv3e] Called com.android.org.conscrypt.OpenSSLSocketImpl.isClosed() (agent) [43hq04cbdn1] Called com.android.org.conscrypt.SslWrapper.write(java.io.FileDescriptor, [B, int, int, int) (agent) [yz4ikx9fcpb] Called com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read([B, int, int) (agent) [nrgnn66lv3e] Called com.android.org.conscrypt.OpenSSLSocketImpl.isClosed() (agent) [43hq04cbdn1] Called com.android.org.conscrypt.SslWrapper.read(java.io.FileDescriptor, [B, int, int, int)
android hooking watch class_method com.android.org.conscrypt.ConscryptFileDescriptorSocket.$init
在安卓10上结果
(agent) [4816499695697] Called javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier() com.roysue.httpsocket on (google: 10) [usb] # (agent) [4816499695697] Called javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory() (agent) [4816499695697] Called javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory() com.roysue.httpsocket on (google: 10) [usb] # (agent) [2575726777846] Called com.android.org.conscrypt.OpenSSLSocketImpl.isClosed() (agent) [2575726777846] Called com.android.org.conscrypt.OpenSSLSocketImpl.isInputShutdown() (agent) [2575726777846] Called com.android.org.conscrypt.OpenSSLSocketImpl.isOutputShutdown() (agent) [4979599214099] Called com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write([B, int, int) (agent) [2575726777846] Called com.android.org.conscrypt.OpenSSLSocketImpl.isClosed() (agent) [1105531481296] Called com.android.org.conscrypt.NativeSsl.write(java.io.FileDescriptor, [B, int, int, int) (agent) [1105531481296] Called com.android.org.conscrypt.NativeSsl.isClosed() (agent) [7367730933988] Called com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read([B, int, int) (agent) [2575726777846] Called com.android.org.conscrypt.OpenSSLSocketImpl.isClosed() (agent) [1105531481296] Called com.android.org.conscrypt.NativeSsl.read(java.io.FileDescriptor, [B, int, int, int) (agent) [1105531481296] Called com.android.org.conscrypt.NativeSsl.isClosed()
frida -UF -l hookSocket.js
打印http抓包的结果
1 2 plugin wallbreaker objectsearch com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream plugin wallbreaker objectdump --fullname 0x3486
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function hook_SSLsocketandroid8(){ Java.perform(function(){ console.log("hook_SSLsocket") Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function(bytearry,int1,int2){ var result = this.write(bytearry,int1,int2); // console.log("HTTPS write result,bytearry,int1,int2=>",result,bytearry,int1,int2) // var ByteString = Java.use("com.android.okhttp.okio.ByteString"); // console.log("bytearray contents=>", ByteString.of(bytearry).hex()) //console.log(jhexdump(bytearry,int1,int2)); // console.log(jhexdump(bytearry)); // com.android.org.conscrypt.ConscryptFileDescriptorSocket this$0 console.log(this.this$0.value.sslSession.value.peerHost.value) console.log(this.this$0.value.sslSession.value.peerPort.value) console.log(this.this$0.value.sslSession.value.getProtocol()) console.log(this.this$0.value.sslSession.value.getRequestedServerName()) console.log(JSON.stringify( this.this$0.value.sslSession.value.getStatusResponses())) console.log(this.this$0.value.sslSession.value.getValueNames().toString()) return result; } // Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function(bytearry,int1,int2){ // var result = this.read(bytearry,int1,int2); // console.log("HTTPS read result,bytearry,int1,int2=>",result,bytearry,int1,int2) // var ByteString = Java.use("com.android.okhttp.okio.ByteString"); // console.log("bytearray contents=>", ByteString.of(bytearry).hex()) // //console.log(jhexdump(bytearry,int1,int2)); // // console.log(jhexdump(bytearry)); // return result; // } }) }
frida 14
android hooking watch class_method com.android.org.conscrypt.ConscryptFileDescriptorSocket.$init
objection -g com.onejane.httpsocket explore -s “android hooking watch class_method com.android.org.conscrypt.ConscryptFileDescriptorSocket.$init”
File appmon 中用到的API。12.8.0报错就切到14.*,frida -UF -l HTTP.js
和frida -UF -l Storage.js
和frida -UF -l SharedPreferences.js
Mobile-Security-Framework-MobSF
1 2 3 objection -g comoolapk.market explore -s "android hooking watch class android.content.ContextWrapper" 下载app查看agent用到的api android hooking watch class_method android.content.ContextWrapper.getDataDir --dump-args --dump-backtrace --dump-return android hooking watch class_method android.content.ContextWrapper.getCacheDir --dump-args --dump-backtrace --dump-return
1 2 3 4 frida-ps -U|grep -i gravity objection -g com.ceco.oreo.gravitybox explore android hooking watch class android.app.SharedPreferencesImpl android hooking watch class_method android.app.SharedPreferencesImpl.getString --dump-args --dump-backtrace --dump-return
通杀 1 2 3 4 5 6 7 8 9 objection -g comoolapk.market explore android hooking search classes File android hooking watch class java.io.File android hooking watch class_method java.io.File.getPath --dump-args --dump-backtrace --dump-return android hooking watch class_method java.io.File.delete --dump-args --dump-backtrace --dump-return android hooking watch class_method java.io.File.exists --dump-args --dump-backtrace --dump-return android hooking watch class_method java.io.File.list --dump-args --dump-backtrace --dump-return android hooking watch class_method java.io.File.getName --dump-args --dump-backtrace --dump-return cat objection.log | grep Return
java.io.File java.lang.String
1 2 3 4 5 6 7 8 9 10 11 android hooking watch class java.lang.String android hooking watch class_method java.lang.String.toString --dump-args --dump-return 安卓8 android hooking watch class_method java.lang.String.equals --dump-args --dump-return 安卓10 android hooking watch class_method java.lang.StringBuilder.$init --dump-args --dump-return android hooking watch class android.telephony.TelephonyManager 获取硬件信息 plugin wallbreaker objectsearch android.telephony.TelephonyManager plugin wallbreaker objectdump 0x4563 plugin wallbreaker classdump android.os.Build android hooking watch class_method android.telephony.TelephonyManager.getDeviceId --dump-args --dump-backtrace --dump-return frida -U -f com.coolapk.market -l File.js --no-pause -o file.txt frida -UF -l File.js --no-pause -o file.txt
利用Frida修改Android设备的唯一标识符
修改Build.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private static String getString(String property) { String result = SystemProperties.get(property, UNKNOWN) ; if(property.equals("ro.product.brand")){ result = new String("r0ysueBRAND"); }else if(property.equals(("ro.product.manufacturer"))){ result = new String("r0ysueMANUFACTUERER"); }else if(property.equals("ro.product.board")){ result = new String("r0ysueBOARD"); }else if(property.equals("no.such.thing")){ result = new String("r0ysueAAAABBBBCCCCDDDD"); } Exception e = new Exception("r0ysueFINGERPRINT"); e.printStackTrace(); return result; } @RequiresPermission(Manifest.permission.READ_PHONE_STATE) public static String getSerial() { IDeviceIdentifiersPolicyService service = IDeviceIdentifiersPolicyService.Stub .asInterface(ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE)); try { String result =service.getSerial(); return "r0ysueserial1234"; } catch (RemoteException e) { e.rethrowFromSystemServer(); } return UNKNOWN; }
修改TelephonyManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getSimSerialNumber(int subId) { try { IPhoneSubInfo info = getSubscriberInfo(); String resutlt = info.getIccSerialNumberForSubscriber(subId, mContext.getOpPackageName()); if (info == null) return null; return "r0ysueSERIALAAAABBBB"; } catch (RemoteException ex) { return null; } catch (NullPointerException ex) { // This could happen before phone restarts due to crashing return null; } } @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceId() { try { ITelephony telephony = getITelephony(); String result = telephony.getDeviceId(mContext.getOpPackageName()); if (telephony == null) return null; return "r0ysueIMEI"; } catch (RemoteException ex) { return null; } catch (NullPointerException ex) { return null; } }
. build/envsetup.sh
lunch aosp_bullhead-user
m
替换编译生成img到官方镜像包中,重新打包成image-bullhead-opm1.171019.011.zip
./flash-all.sh
指纹识别技术安全分析
对抗 不检测root,检测aosp,正常人不会用aosp,App
可以通过判断java.net.NetworkInterface.getName()是否等于“tun0”或“ppp0”来判断是否存在VPN。Bypass也很简单,hook该api使其返回“rmnet_data1”,即可达到过vpn检测目的。
风险控制笔记
自制沙箱 检测Android虚拟机的方法和代码实现
2020年安卓源码编译指南
Android 应用多开对抗实践
使用手机连接charles的代理,chsl.pro/ssl安装证书。
1 2 3 4 5 6 cd /data/misc/user/0/cacerts-added 查看新安装的证书a27a90a2.0 cp a27a90a2.0 /sdcard/Download cd Desktop/asop810r1/system/ca-certificates/files adb pull /sdcard/Download/a27a90a2.0 . build/envsetup.sh lunch aosp_bullhead-user 编译,没有root,使用user-debug有root
修改KeyStore.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public final void load(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { if (password != null) { String inputPASSWORD = new String(password); Class logClass = null; try { logClass = this.getClass().getClassLoader().loadClass("android.util.Log"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Method loge = null; try { loge = logClass.getMethod("e", String.class, String.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } try { loge.invoke(null, "r0ysueKeyStoreLoad", "KeyStore load PASSWORD is => " + inputPASSWORD); Exception e = new Exception("r0ysueKeyStoreLoad"); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } Date now = new Date(); String currentTime = String.valueOf(now.getTime()); FileOutputStream fos = new FileOutputStream("/sdcard/Download/" + inputPASSWORD + currentTime); byte[] b = new byte[1024]; int length; while ((length = stream.read(b)) > 0) { fos.write(b, 0, length); } fos.flush(); fos.close(); } keyStoreSpi.engineLoad(stream, password); initialized = true; }
修改SocketOutputStream.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 private void socketWrite(byte b[], int off, int len) throws IOException { if (len <= 0 || off < 0 || len > b.length - off) { if (len == 0) { return; } throw new ArrayIndexOutOfBoundsException("len == " + len + " off == " + off + " buffer length == " + b.length); } FileDescriptor fd = impl.acquireFD(); try { BlockGuard.getThreadPolicy().onNetwork(); socketWrite0(fd, b, off, len); if(len>0){ byte[] input = new byte[len]; System.arraycopy(b,off,input,0,len); String inputString = new String(input); Class logClass = null; try { logClass = this.getClass().getClassLoader().loadClass("android.util.Log"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Method loge = null; try { loge = logClass.getMethod("e",String.class,String.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } try { loge.invoke(null,"r0ysueSOCKETrequest","Socket is => "+this.socket.toString()); loge.invoke(null,"r0ysueSOCKETrequest","buffer is => "+inputString); Exception e = new Exception("r0ysueSOCKETrequest"); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } catch (SocketException se) { if (se instanceof sun.net.ConnectionResetException) { impl.setConnectionResetPending(); se = new SocketException("Connection reset"); } if (impl.isClosedOrPending()) { throw new SocketException("Socket closed"); } else { throw se; } } finally { impl.releaseFD(); } }SocketOutputStream
修改SocketInputStream.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private int socketRead(FileDescriptor fd, byte b[], int off, int len, int timeout) throws IOException { int result = socketRead0(fd, b, off, len, timeout); if(result>0){ byte[] input = new byte[result]; System.arraycopy(b,off,input,0,result); String inputString = new String(input); Class logClass = null; try { logClass = this.getClass().getClassLoader().loadClass("android.util.Log"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Method loge = null; try { loge = logClass.getMethod("e",String.class,String.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } try { loge.invoke(null,"r0ysueSOCKETresponse","Socket is => "+this.socket.toString()); loge.invoke(null,"r0ysueSOCKETresponse","buffer is => "+inputString); Exception e = new Exception("r0ysueSOCKETresponse"); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } return result; }
修改SslWrapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 int read(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis) throws IOException { int result = NativeCrypto.SSL_read(ssl, fd, handshakeCallbacks, buf, offset, len, timeoutMillis) ; if(result>0){ byte[] input = new byte[result]; System.arraycopy(buf,offset,input,0,result); String inputString = new String(input); Class logClass = null; try { logClass = this.getClass().getClassLoader().loadClass("android.util.Log"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Method loge = null; try { loge = logClass.getMethod("e",String.class,String.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } try { loge.invoke(null,"r0ysueSOCKETresponse","SSL is =>"+this.handshakeCallbacks.toString()); loge.invoke(null,"r0ysueSOCKETresponse","buffer is => "+inputString); Exception e = new Exception("r0ysueSOCKETresponse"); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } return result; } void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis) throws IOException { if(len>0){ byte[] input = new byte[len]; System.arraycopy(buf,offset,input,0,len); String inputString = new String(input); Class logClass = null; try { logClass = this.getClass().getClassLoader().loadClass("android.util.Log"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Method loge = null; try { loge = logClass.getMethod("e",String.class,String.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } try { loge.invoke(null,"r0ysueSSLrequest","SSL is => "+this.handshakeCallbacks.toString()); loge.invoke(null,"r0ysueSSLrequest","buffer is => "+inputString); Exception e = new Exception("r0ysueSSLrequest"); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } NativeCrypto.SSL_write(ssl, fd, handshakeCallbacks, buf, offset, len, timeoutMillis); }
编译好后,刷机,安装soul使用chales抓包,查看logcat中Keystore加载的Password,下载下来的证书改名为soul.p12安装实现App客户端证书文件和密码自吐
1 2 make update-api 由于修改了文件,更新api m
甲方风控 实用FRIDA进阶:内存漫游、hook anywhere、抓包
r0capture git clone https://github.com/r0ysue/r0capture.git
仅限安卓平台,测试安卓7、8、9、10、11 可用 ; 无视所有证书校验或绑定,不用考虑任何证书的事情; 通杀TCP/IP四层模型中的应用层中的全部协议; 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本; 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等; 无视加固,不管是整体壳还是二代壳或VMP,不用考虑加固的事情; python r0capture.py -U cn.soulapp.android -v
python r0capture.py -U -f com.qiyi.video -v
frida -UF -l hookSSLSocket.js
frida -U -f cn.soulapp.android -l saveClientCet.js –no-pause 增加客户端证书dump功能
adb pull /sdcard/Download/ff93e99.p12
证书转换工具 支持bks to p12 把安卓转成Charles支持的p12
charles-Proxy-SSL Proxying Settings-Client Certificates-Create Secure Store-设置自定义密码,配置Host/Port为*对任何IP任何端口使用该证书,Import P12-填入SSL Certificate Password,即抓到的key密码
为沙箱增加调用栈dump证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 function hook_KeyStore_load() { Java.perform(function () { var ByteString = Java.use("com.android.okhttp.okio.ByteString"); var myArray=new Array(1024); var i = 0 for (i = 0; i < myArray.length; i++) { myArray[i]= 0x0; } var buffer = Java.array('byte',myArray); var StringClass = Java.use("java.lang.String"); var KeyStore = Java.use("java.security.KeyStore"); KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); console.log("KeyStore.load1:", arg0); this.load(arg0); }; KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) { console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null); if (arg0){ var file = Java.use("java.io.File").$new("/sdcard/Download/"+ String(arg0)+".p12"); var out = Java.use("java.io.FileOutputStream").$new(file); var r; while( (r = arg0.read(buffer)) > 0){ out.write(buffer,0,r) } console.log("save success!") out.close() } this.load(arg0, arg1); }; console.log("hook_KeyStore_load..."); // android.content.res.AssetManager$AssetInputStream@9b10ad6 bxMAFPL9gc@ntKTqmV@A // android.content.res.AssetManager$AssetInputStream@41ce8f6 }%2R+\OSsjpP!w%X // android.content.res.AssetManager$AssetInputStream@54858e6 cods.org.cn }); }
重新使用charles即可抓到soul包
1 2 3 4 5 6 7 objection -g cn.soulapp.android explore android hooking search classes keystore 将打印的类放到文keystore.txt件中,批量hook,前面加上android hooking watch class objection -g cn.soulapp.android explore -c keystore.txt plugin wallbreaker objectsearch java.security.KeyStore$PrivateKeyEntry plugin wallbreaker objectdump --fullname 0x0123u android hooking watch class_method java.security.KeyStore$PrivateKeyEntry.getCertificateChain --dump-args --dump-backtrace --dump-return android hooking watch class_method java.security.KeyStore$PrivateKeyEntry.getPrivateKey --dump-args --dump-backtrace --dump-return
QtScrCpy 手机投屏 Linux版
1 2 apt install libsdl2-2.0-0 ./run x
为沙箱增加客户端证书DUMP的功能
1 2 3 4 5 6 7 frida -U -f cn.soulapp.android -l 2021trace.js --no-pause -o traceresult.txt 查看java.security.KeyStore$PrivateKeyEntry的调用栈 objection -g cn.soulapp.android explore plugin load /root/Desktop/Wallbreaker plugin wallbreaker objectsearch java.security.KeyStore$PrivateKeyEntry plugin wallbreaker objectdump --fullname 0x123f 查看KeyStore$PrivateKeyEntry类的privateKey和publicKey android heap search instances java.security.KeyStore$PrivateKeyEntry android heap execute 0x2ce7 getPrivateKey() 主动调用
js实现,frida -U- f cn.soulapp.android -l savePrivateKey.js --no-pause
打开app后查看/data/local/tmp/soul下的证书文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 setImmediate(function () { Java.perform(function () { console.log("Entering") Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () { console.log("Calling java.security.KeyStore$PrivateKeyEntry.getPrivateKey method ") var result = this.getPrivateKey() console.log("toString result is => ", result.toString()) storeP12(this.getPrivateKey(),this.getCertificate(),'/data/local/tmp/soul'+uuid(10,16)+'.p12','hello'); return result; } Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () { console.log("Calling java.security.KeyStore$PrivateKeyEntry.getCertificateChain method ") var result = this.getCertificateChain() storeP12(this.getPrivateKey(),this.getCertificate(),'/data/local/tmp/soul'+uuid(10,16)+'.p12','hello'); return result; } }) }) function storeP12(pri, p7, p12Path, p12Password) { var X509Certificate = Java.use("java.security.cert.X509Certificate") var p7X509 = Java.cast(p7, X509Certificate); var chain = Java.array("java.security.cert.X509Certificate", [p7X509]) var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC"); ks.load(null, null); ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain); try { var out = Java.use("java.io.FileOutputStream").$new(p12Path); ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray()) } catch (exp) { console.log(exp) } } function uuid(len, radix) { var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); var uuid = [], i; radix = radix || chars.length; if (len) { // Compact form for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]; } else { // rfc4122, version 4 form var r; // rfc4122 requires these characters uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; uuid[14] = '4'; // Fill in random data. At i==19 set the high bits of clock sequence as // per rfc4122, sec. 4.1.5 for (i = 0; i < 36; i++) { if (!uuid[i]) { r = 0 | Math.random() * 16; uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; } } } return uuid.join(''); }
adb pull /data/local/tmp
使用KeyStore Explorer打开,密码是hello
charles-手机连接代理-SSL Proxying Settings-Client Certificates-Add-Import P12-密码hello-Host和Port配置*,启动Postern,启动soul成功抓包
adb install dida.apk 通过top查看包名cn.ticktick.task
1 objection -g cn.ticktick.task explore -s "android hooking watch class_method java.io.File.\$init --dump-args --dump-return --dump-backtrace"
frida -U -f cn.ticktick.task -l sslpinninghelper.js --no-pause
打印证书路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 setImmediate(function(){ Java.perform(function(){ Java.use("java.io.File").$init.overload('java.io.File', 'java.lang.String').implementation = function(file,cert){ var result = this.$init(file,cert) var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); if(file.getPath().indexOf("cacert")>0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted")> 0){ console.log("path,cart",file.getPath(), cert) console.log(stack); } return result; } }) })
SSL pinning helper 帮助定位证书绑定的关键代码,在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式
pm -l | grep -i soul
pm -l | grep -i ticktick
pip install hexdump
python r0capture.py -U -f cn.soulapp.android -v >>sout.txt 重新抓包,frida 14.0.8
python r0capture.py -U -f cn.ticktick.task -v >>tick.txt
框架层抓包 沙箱SslWrapper.java 的修改等同于hook Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int')
实现定位收发包函数的功能。
基于trace的内存漫游确认Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain
客户端证书dump导出功能。
增加混淆后的SSLping代码定位功能stack.indexOf("X509TrustManagerExtensions.checkServerTrusted")
抓包沙箱植入根证书绕过客户端校验服务器,cd aosp810r1/system/ca-certificates/files
根证书目录,将charles的证书下载到该文件目录下,编译生成镜像,形成中间人
抓包沙箱导出客户端证书绕过服务器校验客户端,KeyStore.java 有个内部方法PrivateKeyEntry
KeyStore.java 去除上面自制沙箱时 public final void load(InputStream stream, char[] password)
的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public PrivateKey getPrivateKey() { String p12Password = "r0ysue"; Date now = new Date(); String currentTime = String.valueOf(now.getTime()); String p12Path = "/sdcard/Download/tmp" + currentTime + ".p12"; X509Certificate p7X509 = (X509Certificate) chain[0]; Certificate[] mychain = new Certificate[]{p7X509}; // 生成一个空的p12证书 KeyStore myks = null; try { myks = KeyStore.getInstance("PKCS12", "BC"); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } try { myks.load(null, null); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } // 将服务器返回的证书导入到p12中去 try { myks.setKeyEntry("client", privKey, p12Password.toCharArray(), mychain); } catch (KeyStoreException e) { e.printStackTrace(); } // 加密保存p12证书 FileOutputStream fOut = null; try { fOut = new FileOutputStream(p12Path); } catch (FileNotFoundException e) { e.printStackTrace(); } try { myks.store(fOut, p12Password.toCharArray()); } catch (KeyStoreException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } return privKey; }
抓包沙箱之定位(混淆后的)SSLpinning代码,修改File.java,去除上面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public File(File parent, String child) { if (child == null) { throw new NullPointerException(); } if (parent != null) { if (parent.path.equals("")) { this.path = fs.resolve(fs.getDefaultParent(), fs.normalize(child)); } else { this.path = fs.resolve(parent.path, fs.normalize(child)); } } else { this.path = fs.normalize(child); } Class logClass = null; try { logClass = this.getClass().getClassLoader().loadClass("android.util.Log"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Method loge = null; Method getStackTraceString = null; try { // loge = logClass.getMethod("e", String.class, String.class); getStackTraceString = logClass.getMethod("getStackTraceString",Throwable.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } try { // loge.invoke(null, "r0ysueKeyStoreLoad", "KeyStore load PASSWORD is => " + inputPASSWORD); String stack = (String)getStackTraceString.invoke(null,new Throwable()); if (parent.getPath().indexOf("cacert") >= 0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted") >= 0) { Exception e = new Exception("r0ysueFileSSLpinning"); e.printStackTrace(); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } this.prefixLength = fs.prefixLength(this.path); }
编译刷机
1 2 lunch aosp_bullhead-user m 编译完更新system.img到官方镜像bullhead刷机
默认系统报错400 No required SSL certificate was sent
,导入证书到SSLProxying Setting才能正确抓到soul包
绕过滴答 frida -U -f cn.ticktick.task -l bypassPinning.js --no-pause
默认报错trust the Charles Root Certificate
,客户端收到charles的证书,计算公钥hash后比对结果决定发请求结果。
1 2 3 4 5 6 7 8 9 setImmediate(function(){ Java.perform(function(){ console.log("Bypassing") Java.use("z1.g").a.implementation = function(){ console.log("called here") return; } }) })
HTTPS客户端证书多重证书绑定 咪咕视频 登录抓包,SSL handshake with client failed: An unknown issue occurred processing the certificate (certificate_unknown)
从抓包发现证书 绑定,可能客户端只信任信任的公钥签名,不信任就不允许,停止客户端访问的证书绑定。客户端发了,我们已经绕过了校验,把自己公钥发给charles,charles用自己私钥解开客户端的公钥发现不正常的结果。
1 2 3 4 5 6 dumpsys activity top 查看包名 com.ophone.reader.ui objection -g com.ophone.reader.ui explore android sslpinning disable 需要在启动时运行 objection -g com.ophone.reader.ui explore -s "android sslpinning disable" 在点我登录页面触发解绑定,如果崩溃 objection -g com.ophone.reader.ui explore 在点我登录时开始漫游 android sslpinning disable 解绑定后再获取验证码,再抓包
证书绑定的逻辑没有hook掉:”at com.bangcle.andjni.JniLib.cL (Native Method)” → 只有逆代码来过证书绑定
1 python r0capture.py -U com.ophone.reader.ui -v -p migu.pcap 关闭postern抓包,获取验证码没有更新log,说明没有一些底层的框架
通过wireshark查看migu.pcap结果,发现也没有关键性信息,抓包也抓不到。
1 2 3 4 5 6 python r0capture.py -U -f com.ophone.reader.ui -v 尝试导出证书 frida -U -f com.ophone.reader.ui -l script.js --no-pause adb shell 查看sdcard/Download下的证书 adb pull /sdcard/Download/ophone 下的证书导入到Charles的SSL Proxying Settings中,打开postern抓包 objection -g com.ophone.reader.ui explore 在点我登录时开始漫游 android sslpinning disable 解绑定后再获取验证码抓包即可获取passport.migu.cn:8443的包信息
SSL handshake with server failed - Remote host terminated the handshake The remote SSL server rejected the connection. The server may require a specific certificate or cipher not supported by Charles.
过客户端证书后发现更多证书绑定,Frida.Android.Practice
1 objection -g com.ophone.reader.ui explore -s "android sslpinning disable"
git clone https://github.com/WooyunDota/DroidSSLUnpinning.git
1 frida -U -f com.ophone.reader.ui -l hooks.js --no-pause 抓发送验证码包依旧有请求失败
FRIDA 使用经验交流分享 ,git clone https://github.com/deathmemory/FridaContainer.git
1 2 3 4 cd utils/android frida -U -f com.ophone.reader.ui -l multi_unpinning.js --no-pause objection -g com.ophone.reader.ui explore -s "android hooking watch class_method java.io.File.\$init --dump-args --dump-backtrace --dump-return" 查看证书 frida -U -f com.ophone.reader.ui -l trace.js --no-pause -o ophone.txt 修改trace.js中traceClass("java.io.File"),在traceClass中修改targets=[]只trace init方法,在traceMethod中打开调用栈android.util.log,发送验证码后查看文件,搜索cacert查看调用栈
北京银行 1 2 3 4 python r0capture.py -U -f com.bankofbeijing.mobilebanking -v ./hluda-server-14.2.1-android-arm64 frida -U -f com.bankofbeijing.mobilebanking -l script.js --no-pause -o bjbank.txt frida -U -f com.bankofbeijing.mobilebanking -l trace.js --no-pause -o bjbank2.txt 打开traceClass("java.security.KeyStore$PrivateKeyEntry")
加固厂商自定义开发的证书绑定对抗很难被攻克。