android抓包总结

Posted by marginal on 2023-06-15
Estimated Reading Time 13 Minutes
Words 3.4k In Total
Viewed Times

android抓包基础总结

针对常见的抓包方式以及对抗做了一个总结

抓包方式分类

  • 网卡抓包

    • 肯定能抓到, 但是是未解密的数据, 可能用来抓http比较好使一点
  • Hook数据抓包

    • 如果直接hook底层api可以直接省掉逆向分析, 并且帮助逆向分析程序执行逻辑(使用frida等hook工具来打印堆栈回溯等), 但是hook出来的数据进行分析有些麻烦.
  • 代理抓包

    • 可以直接对https数据包进行解析, 可以很方便的进行查看与修改, 不过这也是反抓包手段的主要目标
  • .…………

代理抓包

原理

代理软件对于客户端app伪造成一个服务器, 对于服务器伪造成一个客户端, 然后再把对应的消息进行转发, 这样可以在正常通信的前提下, 对通信的信息了如指掌.

大概就是这个样子(网上找的图):

image-20230627225130553

对于没有保护的app, 把环境配好就能抓包抓到了, 配合上逆向一倒腾, 许许多多的爬虫就出来了.所以很多app都加了各种各样的校验, 比如服务端验证客户端证书, 客户端验证服务端证书等.

image-20230627225154105

客户端验证服务器证书

这里就用了客户端验证服务器证书, 开发商先讲证书信息内置到app中, 当客户端请求服务器连接并收到服务器证书后, 使用内置的证书信息校验服务器证书是否合法, 如果不合法直接断开连接. 当抓包证书伪装服务器的时候返回的Burp证书肯定是不能通过校验的.

客户端校验服务器失败之后, burp suite一般是没有任何内容的, 客户端直接把连接断掉了

image-20230627225218888

遇到有客户端校验服务器证书的app,可以采取一下几个方案进行绕过:

  • 对app进行逆向并且patch掉校验实现的地方然后重打包
    • 逆向成本有点高, 对于加固和加密的APK耗时耗精力, 不过有时候不得不这样(针对框架完全是开发商自己实现的情况)
  • 使用Frida对应的API进行hook
    • 用到了Frida框架, 所以对root有要求, 并且容易被反调试限制. 现成的针对校验 SSL 证书的 API 的frida脚本网上一搜就有现成的, 不过针对http框架完全是开发商自己实现的情况就需要自己来写了.
  • Xposed框架+JustTrustMe进行绕过
    • JustTrustMe 一个用来禁用、绕过 SSL 证书检查的基于 Xposed 模块。JustTrustMe 也是将 APK 中所有用于校验 SSL 证书的 API 都进行了 Hook,从而绕过证书检查. 不过有动用xposed框架就可能搞成砖块.可以先在模拟器中试试. 一样需要root环境, app可能会有xposed和root检查, 不过这里不会被反调试限制.
  • VirtualApp.
  • …………………………………………….

服务端验证客户端证书

image-20230627225520620

比起单向认证, 加上了服务器也同时需要验证客户端的证书,也就是会要求客户端提供自己的证书,如果没有通过验证,则会拒绝连接,如果通过验证,服务器获得用户的公钥. 客户端这里会内置一套公钥证书+私钥.

对于服务器端验证客户端证书的绕过, 关键在于得到客户端内置的证书以及得到对应的密钥.

Android本地存放的一般是bks或者p12证书文件, 私钥和公钥都在这个证书文件里面. 但是还有一个对证书解密的密钥.

提取公钥证书:

  • 直接逆向证书发送的地方, 找到证书的来源.
  • 一般证书文件可能会放在 assets 或者 res 目录下可以直接搜索证书文件,证书文件格式后缀如:crt、cer、p12、pfx等.
  • 使用hook关键函数来从内存中还原对应的证书与私钥

提取密钥:

  • 直接逆向
  • hook关键函数来获取

Burp在图中这里添加客户端证书:

image-20230627225619020

选择PKCS12格式(之前用openssl生成证书的时候用的就是PKCS12)并输入host, 然后选择对应的证书并且输入密码.

软件默认不走代理

代理抓包的关键就是需要HTTP客户端按照要求去连接代理服务器, 一般情况下是设置的系统代理,通常http客户端都是按要求去实现的,在进行http请求前会先检查系统代理,如果有设置代理,客户端会直接使用完整uri去连接代理服务器。不同的平台通常会实现自己的的http客户端的,虽然他们都按照协议要求实现了代理功能,但是并不一定在默认情况下会直接使用系统代理。比如flutter, 或者开发商自己设置不走代理.我之前逆一个otp生成项目就是这样, 明明开了抓包软件, 而且运行起来没有任何问题, 但是就是没有抓到包. 后面折腾了我几天才知道是软件默认不走代理的原因.

对于不走代理的软件, 有很多解决方法:

  • 可以直接逆向patch它让app强行走系统代理
    • 非常麻烦, 逆向成本也较高.
    • 直接控制dns解析,修改dns的方式让客户端以为我们的代理服务器就是目标服务器.
      • 原本如果在没有代理的情况下,请求的请求行实际上是不完整的url,客户端在发送报文前已经知道服务器的地址并与之建立了连接,没有必要再发送方案,主机名及端口, 这和走代理不一样, 代理服务器没有办法连接正确的服务器。因此客户端发送给代理的请求其实稍有不同,客户端会在请求行里使用完整的uri,这样代理服务器才能解析真实的服务器的地址。所以这样搞需要对应域名修改hosts, 比较难操作, 并且又涉及到dns的知识了.
    • 用vpn进行流量转发, 将流量转发到代理服务器, 比如用droxy, Proxifier等软件.
      • 感觉比前面的方法简单, 只需要配置一下流量转发的vpn软件就好了, 这里具体的配置我就不啰嗦了, 网上一大堆.

反代理抓包实现

证书生成

先用openssl生成两个证书, 分别用于客户端校验服务器与服务器校验客户端.

生成客户端校验服务器证书:

openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt

生成服务器校验客户端证书:

openssl x509 -req –in .csr –out .cer –signkey .key -CAcreateserial -days 3650

转为p12证书, 并且设置密码:

openssl pkcs12 -in client.p12 -out client.pem

获取服务器公钥的sha256值, 可以用于公钥校验:

openssl s_client –connect 域名:端口| openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

这里几点要注意:

  • 首先是域名校验, 需要在代码里面手动去掉, 或者在生成证书的时候加上域名.
  • 校验的时候客户端和服务器使用的证书是同一个.
  • 服务器校验客户端的时候, android本地不能直接用crt证书, 要转为p12证书.
  • P12文件相当于是证书加私钥, 并且还有一个加密整个证书的密码.
  • 如果出现javax.net.ssl.SSLProtocolException: Read error则可能是服务器校验客户端证书错误
  • 如果出现java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.则可能是客户端校验服务器证书错误.

证书固定

Java层发送调用okhttp对服务器证书进行校验,以下是官网手动添加信任证书的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
InputStream openRawResource = getApplicationContext().getResources().getAssets().open(“server.crt");
Certificate ca = CertificateFactory.getInstance("X.509").generateCertificate(openRawResource);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext1 = null;
sslContext1 = SSLContext.getInstance("TLS");
sslContext1.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
OkHttpClient client1 = new OkHttpClient();
OkHttpClient pClient2 = client1.newBuilder().sslSocketFactory(sslContext1.getSocketFactory(), (X509TrustManager) trustManagerFactory.getTrustManagers()[0]).build();
Request request2 = new Request.Builder().url("https://192.168.1.103:4444").build();
//….接下来就是okhttps请求

对公钥进行校验:

1
2
3
CertificatePinner pinner = new CertificatePinner.Builder().add(url, public_key).build();

OkHttpClient pClient = client.newBuilder().certificatePinner(pinner).build();

还有一种更加简单的xml实现方法, 不过也很容易被逆向找到

在xml中对服务器证书进行校验:

1
2
3
4
5
6
7
8
9
10
11
<domain-config>

<domain includeSubdomains="true">url</domain>

<trust-anchors>

<certificates src="@raw/.pem"/>

</trust-anchors>

</domain-config>

Xml中对公钥进行校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
<domain-config>

<domain includeSubdomains="true">url</domain>

<pin-set expiration="2099-01-01"

tools:ignore="MissingBackupPin">

<pin digest="SHA-256">PublicKey</pin>

</pin-set>

</domain-config>

服务器校验客户端证书

用python来启动一个服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
import sys
import ssl
from http.server import HTTPServer, BaseHTTPRequestHandler
#.............................
def main():
server_address = ("0.0.0.0", int(port))
server = HTTPServer(server_address, RequestHandler)
#双向校验
server.socket = ssl.wrap_socket(server.socket, certfile = serverCerts, server_side = True,
keyfile = serverKey,
cert_reqs = ssl.CERT_REQUIRED,
ca_certs = clientCerts,
do_handshake_on_connect = False
)
print("Starting server, listen at: %s:%s" % server_address)
server.serve_forever()

if __name__ == "__main__":
main()

Java层发送证书的okhttp实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
keyStore.load(client_input, KEY_STORE_PASSWORD.toCharArray()); //分别为输入流和密钥

keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray());

sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()); //这里为第一个参数

sslContext.getSocketFactory();

OkHttpClient mClient = client.newBuilder().sslSocketFactory(Objects.requireNonNull(ClientSSLSocketFactory.getSocketFactory(getApplicationContext())), Objects.requireNonNull(trustManager)).hostnameVerifier(new HostnameVerifier() {

@Override

public boolean verify(String hostname, SSLSession session) {

HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();

return hv.verify("www.test.com", session);

}

}).build();

被重写的verify部分就是域名校验, 如果只是图一乐, 搞出来的证书没有包含域名, 那就直接把这里返回true就行, 不然会报错:

javax.net.ssl.SSLPeerUnverifiedException: Hostname xxx not verified_hostname not verified

Hook数据抓包

在调用okhttp进行数据传输的时候, 会调用SSL_read负责读取远程服务器发来的数据而SSL_write则负责写入主机要发送的数据到缓冲区,发送到远程服务器.而SSL_write则负责写入主机要发送的数据到缓冲区,发送到远程服务器。这里读写的数据都是明文的, 所以想办法hook到这两个函数, 也能抓到明文数据包. 不过如果不是调用的libssl.so库而是自己那套ssl库的话, 就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
int SSL_write(SSL *ssl, const void *buf, int num)

这个buf里面就是加密之前的明文报文, num是报文长度. 对这个函数需要在调用之前进行hook.也就是hook代码写在onEnter里面

int SSL_read(SSL *ssl, void *buf, int num)

这个buf里面就是解密之前的秘文报文, num是报文长度. 对这个函数需要在调用之后进行hook.也就是hook代码写在onLeave里面

Frida hook示例:

var sslWritePtr=Module.getExportByName("libssl.so","SSL_write");

var sslReadPtr=Module.getExportByName("libssl.so","SSL_read");

Interceptor.attach(sslWritePtr,{

onEnter:function(args){

var sslPtr=args[0];

var buff=args[1];

var size=args[2];

console.log(hexdump(buff,{length:size.toInt32()}));

},

onLeave:function(retval){

}

});

总结

如果发生代理抓包没有抓到, 我一般是按照以下步骤进行排查:

  1. 首先排查网络环境, 代理是不是没通网, 代理是不是没配置正确, 是否安装代理软件的证书为系统证书.
    • 对应android小于7.0的版本, 系统不在信任用户CA证书, 所以需要安装系统证书(需要root), 像我这个调试机就是root不完全的, 不能直接安装系统CA证书, magisk有个工具movecert可以解决这一点.
  2. 看一下是不是有其他地方的检查, 比如root检查, xposed检查, 代理检查……, 这些很多都有大哥写好的轮子, 到时候直接调用就行.
  3. 检查是不是错误, 比如手机app提示网络错误, 或者抓包软件里面服务器响应为400, 如果没有, 做一下vpn流量转发或者使用其他代理流量的工具, 有一些app在和http服务器通讯的时候, 默认不会走代理的通信.
  4. 这时候优先考虑本地校验服务器证书, 也就是证书固定, 尝试用frida脚本或者justTrustme来绕过本地校验服务器证书.有服务器校验客户端证书的app一般都会有客户端校验服务器, 以为客户端校验服务器证书比服务器校验客户端证书用的成本更低.
  5. 在这里再看一下是不是抓到包但是数据包是不是报错
  6. 如果服务器响应报错为400, 那应该是服务器校验客户端证书失败, 去寻找对应的公钥证书以及密钥, 并且导入到抓包软件.
  7. 报错还是为app网络错误, 那应该是客户端校验服务器证书失败, 多半是前面的绕过本地校验服务器证书没有成功, 很有可能是开发商自己实现的通讯模块, 需要自己慢慢对app进行逆向再编写hook脚本了.
  8. 考虑使用hook抓包, 或者此通信非https协议.

如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !