最近学习了各种抓包。为了防止忘记。学到的东西必须是整理一波啊。向大佬们看齐。如果有啥地方写的不对,希望大家多多指点
抓包主要是针对网络通讯数据,客户端向服务端上报的数据拦截下来。一般都是抓取http、https、tcp、udp。想要获取到数据包。有多种方式,下面简单列一下。
抓包方式
1、hook app业务层,根据业务代码逻辑找到触发请求的函数,比如按钮触发,或者触发数据上报时的提示框等方式。分析后找到发送数据的地方hook打印。
优点:不受app的防抓包手段的影响,只要能hook到就能抓到包。
缺点:必须分析app找关键点,并且每个不同的请求都要找对应的触发函数,效率太慢。
2、系统框架层的hook。直接hook系统源码发送和接受数据的地方。
优点:可以直接省略掉业务层的分析。因为业务层不论逻辑怎么样最终都是调用系统的或者是第三方的库来进行数据传输。并且通用性更好。基本不用修改就可以抓很多app的包。并且可以在这里直接打印堆栈回溯请求触发的函数,提高分析的效率。同样不受防抓包手段影响。
缺点:hook出来的抓包数据不便于我们分析和筛选。只能在日志中查找对应的数据,分析数据包会比较繁琐。简单的需求或者是溯源时使用比较好
3、中间人抓包,使用charles、burp等抓包工具进行拦截,中间人抓包在https请求时,抓包软件的证书在中间冒充服务端接收客服端的请求。然后又冒充客户端,发送请求给服务端。在中间拿到了客户端和服务端的数据。
优点:专业的抓包分析软件,更加友好的分析页面。更加强大的功能,例如可以拦截请求进行改写替换,安装证书可以解析抓到的https数据包。支持vpn抓包
缺点:防抓包手段针对的主要目标,例如服务端验证客户端证书,不正确就拒绝访问,我们需要把客户端的证书给dump出来,然后让中间人抓包使用指定证书。或者是客户端验证服务端证书。我们需要找到并hook去掉验证的代码。无法抓tcp和udp的包
4、网卡抓包、路由抓包。如wireshark,科来之类的。
优点:这种方式不受任何限制,并且通杀,绝对能抓到。
缺点:对于加密的数据没有办法。需要自己进行解密,对于http和https包的展示不太友好
其中比较通用一点的是系统层的hook。所以我这里主要针对这个记录下系统层hook的方式抓取http、https、tcp、udp的数据。
系统层hook抓包
1、java层的抓包
首先贴一个网上找的经典的网络模型图。可以看出来。http包是处于应用层的一种封装,所以我们抓tcp包的时候就可以抓到http包。
最常听到别人说的一句话就是想要逆向先会正向。我们得先知道如何在android中发包。下面我直接封装两种不同库的http请求方式和一个tcp的请求。然后再顺着代码去分析。看是否能直接在一个地方hook。将这三种情况的包都能抓到。
先贴上用来测试当tcp服务端的代码。我在网上随便搜的:https://github.com/fschr/simpletcp.git
下面是我拿来测试的tcp server代码
1 2 3 4 5 6 7 8 9 |
|
然后下面贴上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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
|
上面列出了HttpURLConnection请求。OkHttp3的库来请求。以及一个tcp的请求。我们知道http最终也是调用的tcp连接来传输的。所以我们直接分析tcp的调用流程即可。在调用链中。任意一个含有我们传递参数的位置都可以打印出想要的明文信息。但是我们要尽量的找一个更通用一些。能获取到数据更完整的点来hook。所以我们找到最后调用的native的位置。下面列出我们接下来的目标
1、找到tcp请求发送数据的native函数处
2、hook函数打印发送的数据以及目标服务器地址、端口。
3、找到tcp请求接受数据的native函数处
4、hook函数打印接收的数据以及目标服务器地址、端口。
目标服务器地址和端口我们直接hook了Socket的构造函数即可拿到。所以直接分析发送数据部分。
1 2 3 4 5 6 7 |
|
然后结果如下。
1 2 |
|
我们先看看write的调用链。整理后如下
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 |
|
所以我们发送数据抓包可以通过hook函数socketWrite0来获取
接下来是接受数据readline的流程,这里比上面稍微复杂一些。我会列出完整的来龙去脉
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 |
|
根据上面我们的线索。下面开始写代码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 49 50 51 52 53 |
|
最后跑一下。将结果存储到文本中。搜索一下我们访问的两个地址http://missking.cc/
以及http://10.ip138.com
还有tcp服务器返回是否有收到
上面的日志显示。成功抓到了两种http请求和tcp的包
2、jni层抓http包
有的时候发包的步骤并不是在java层中。而是在jni层直接就发包了。所以我们即使在java层的最深处。也没法抓到。准备一个例子
所以我们继续追踪之前的http发包例子的后续。socketWrite0和socketRead0的c层的调用链。通过编译配置,可以层层筛选找到代码在libopenjdk.so中。从当前测试手机中导出这个so文件。用ida打开后直接搜索就能找到socketWrite0。
socketWrite0解析的结果后发现调用的是NET_Send。再往后调用libc的sendto来进行发送数据
socketRead0解析的结果后发现调用的是NET_Read。再往后调用libc的recvfrom来进行发送数据
最后开始写代码来处理。
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 |
|
最后的hook结果。这里注意。我们刚刚梳理的jni调用流程是http的。所以测试程序的请求连接要修改下。
3、java层ssl抓包
https实际上就是http+ssl。由于http发送的数据直接就是明文。安全性非常差。https会在数据发送前,先用ssl进行加密。如下图
而加密则是使用对应的证书。接受到服务端数据后。再使用证书来进行解密
下面是https交互的流程。ssl会先将证书中的公钥发送给服务器。然后服务器将自己的公钥返回给客户端。然后客户端拿到公钥后和证书中的私钥计算出共享密钥。然后就使用共享密钥来加解密服务端传递来的数据。服务端同样也是拿到客户端的公钥,就和自己的私钥计算出共享密钥。
看完https的理论部分。我们就清楚如何达到自己的目的了。实际上只要在数据加密前的函数调用流程任意一个环节hook都能抓到明文。
下面直接修改访问地址成https。由于有些证书是手机内置的无需我们自行添加证书。所以代码不需要修改。
1 2 |
|
请求地址修改后。重新使用上面的脚本来hook。发现无法再抓到tcp的请求数据了。说明调用链发生了变化。所以我们分析下访问https时的调用链
由于这个调用链比较长。我就只针对OkHttp3进行跟踪分析。这里我采用的是调试的方式来追踪这个函数。由于整个追踪跳转的较多。我就只针对重点部分进行记录了。在开始调试之前。我们先整理清楚自己的目的。
1、调试GetByOkHttp执行的流程。在执行过程中找到ssl相关的处理。
2、追踪ssl相关的处理。找到最后调用的native函数。
那么下面贴上调试时得到的信息
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
|
根据上面的一连串翻找。终于走完了https请求时的java部分的整个调用链。至于读取包的。可以直接猜测一下,看有没有对应的SSL_read。搜了一下。果然是有的。很可能就是这个
1 2 |
|
接下来开始写hook脚本来处理SSL_write和SSL_read。在这之前。我们要先找到NativeCrypto类的包名才行。用frida来搜索一下
1 2 3 4 5 6 7 8 9 10 |
|
最后得到结果com.android.org.conscrypt.NativeCrypto
然后来处理读取和写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
然后这里就能获取到https的包数据了。但是如果是tcp的情况会有个问题。就是没有连接的服务器ip地址和端口。在整个调用链环节中。我们可以找个尽量靠近native层调用,并且能取到服务器地址和端口的函数来hook。比如直接hook了SSLOutputStream的write和SSLInputStream的read。我们先看看断点调试走到最后的SLLOutPutStream的那里。然后展开这个out对象看看有些什么属性
看到在out里面有个this$0里面的socket有我们想要的目标服务器地址和端口。这个$0实际上是指向当前这个类中类的父级对象。那么我们再hook一下这里
老样子先用之前的办法取到完整类名com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
最后看看效果。能成功抓到请求了。
4、jni层抓https包
继续跟着前面的线索往jni层跟踪。前面我们看到的最后一层是SSL_write和SSL_read,所属类是NativeCrypto。直接搜索函数NativeCrypto_SSL_write。下面贴出关键代码
1 2 3 4 5 6 7 8 9 |
|
继续查看sslWrite的实现。发现里面又调用了一层SSL_write。这个函数虽然和上面的函数同名。但是不是同一个了。该函数是boringssl中的了。这是谷歌创建的openssl的分支,内置在android中的。
1 2 3 4 5 6 |
|
继续看boringssl中的SSL_write。这里调用的函数是根据不同ssl版本调用的。所以找对应的函数应该带上对应版本比如ssl3_write_app_data
1 2 3 4 5 6 |
|
继续看ssl3_write_app_data的实现
1 2 3 4 5 6 |
|
继续看do_ssl3_write的实现。这里可以看到这里就是明文的终点了。再往后面去的函数数据都是密文的了。从这里往前的流程都是明文。可以找任意一个觉得合适的地方来hook。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
在开始写hook处理前解决一个疑问。就是之前在recvfrom和sendto进行hook。并没有取到https的密文。所以我们去看一下。boringssl中是怎么处理最终发送数据的。在boringssl项目的文件crypto/bio/socket.c
中
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 |
|
这里可以看到如何是jni层的ssl里面最终发送数据的地方。判断了如果是win平台就用send和recv。否则就使用write和read。这样我们就知道jni层的ssl怎么抓明文和密文了。明文的hook点直接选择openssl调用的函数SSL_write和SSL_read接下来开始写代码。下面贴上完整的代码
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
|
跑起来后的结果如下。成功抓到ssl的密文和ssl的明文。想要回溯的话直接加上打印堆栈即可。
中间人抓包
我们常常用的burp和charles就是属于中间人抓包。前面我们看过了https的加密方式,需要使用证书的公钥去交换加密再发送数据。那么这些工具是如何做到抓取https的数据包并解密的呢。方式就和他的名字一样。这个抓包的应用他也有一个证书。然后客户端向服务端发送数据时,中间人就假装自己是服务端。将自己证书的公钥发送给客户端,然后拿到客户发送的数据后,再假装自己是客户端,把自己的证书公钥发送给服务端。大概可以想象成一个双面间谍。客户端面前冒充服务端,服务端面前冒充客户端。下面贴上网上找的交互的示意图,包括握手流程都写的非常详细了。
而使用中间人抓包。常常会碰到防抓包手段。这些手段一般都是针对证书的检测。
1、服务端验证客户端证书
服务端会检测客户端交互使用的证书是不是正确的。这种情况我们可以直接将客户端里面使用的证书导出来,然后设置让charles使用指定的证书来抓包。这里导出证书的办法有两种。
第一种是直接hook代码中设置证书的地方,直接重新设置一次空的证书。让其不要验证。
这里看一个网上找的android设置证书的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
大概意思是从资源文件加载到证书。然后用keyStore加载证书。后面再使用。所以我们直接看看keyStore的load函数
1 2 3 4 5 6 |
|
还有其他方式加载证书。看下面的我网上翻的另外一个例子
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 |
|
发现这个例子里面的keyStore.load是null。是在后面进行再设置进去的。所以如果我们处理load函数。就会没有啥效果了。我们可以找找其他hook点。比如我直接在两个init的地方进行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 |
|
然后我们测试下自己的例子。frida -U --no-pause -f com.example.zhuabao -l zhuabao.js
1 2 |
|
最后导出来的文件。我对比了一下原文件。发现要去掉头部的0x43个字节(这里是因为我安卓代码里面设置了别名)和尾部的0x15个字节。就和原证书文件完全一致了。不过是否通用就不知道了。未测试多个样本。
第二种是直接解压apk。然后在里面搜索证书特征的文件。例如下面。大概搜一下一些证书的后缀。一般也可能直接找到。
2、客户端验证服务端证书
大概就是客户端向服务端请求。然后服务端把自己的证书返回了。客户端验证一下证书是否正确有效。没啥问题才正常通讯。而这种判断逻辑直接在客户端的。那就直接hook修改让他不要处理就行了。
解决这个的开源项目有很多。
xposed解决方案:JustTrustMe
frida解决方案:DroidSSLUnpinning