APP测试抓包常见问题总结

平常在进行APP测试的时候发现存在了很多问题,现在总结记录一下对应的知识,都在这遍文章下记录更新,老规矩先放一张图

image-20211206111758936

抓包

校验服务端HTTPS证书

导入Burp证书

image-20211206111913609

导出之后使用

adb push xxx.cer sdard

然后在手机设置中找到安全,选择从SD卡安装证书

image-20211206112049040

也可以开启代理,手机访问 ip:port 手动下载

image-20211206112158670

HTTPS证书绑定

只认定特定的HTTPS证书,其他证书全部拒绝连接

ROOT设备安装Xposed后安装JustTurstMe

网上文章比较多可以自己搜索

eg:https://zhuanlan.zhihu.com/p/36538699

非ROOT设备使用VirtualXposed安装JustTrustMe

网上文章比较多可以自己搜索

eg:

VirtualXposed :https://nsapps.cn/index.php/archives/23/

太极:https://bbs.pediy.com/thread-258036.htm

HTTPS双向证书绑定

HOOK对应的证书加载函数,导入burp进行抓包

KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) 
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1)

image-20211220165122008

可以看到成功hook到证书和密码,保存并导入User options-TLS-Client TLS Certificates即可

检测代理

很多APP中会设置如下检测:

String property = System.getProperty("https.proxyHost");
String property = System.getProperty("https.proxyPort");
if(!TextUtils.isEmpty(property)){
return new Proxy(Proxy,Type.HTTP, new InetSockerAddress(Property, Integer.parseInt(property2)))
}

安装ProxyDroid

下载地址:https://proxydroid.cn.uptodown.com/android

打开之后授予root权限

image-20211206114924471

1). 对ProxyDroid进⾏配置(基本配置)

Auto Setting不勾选,我们⼿动进⾏配置。

Host:输⼊代理服务器IP。

Port:输⼊代理服务器端⼝。

Proxy Type选择代理服务器提供服务类型:我们这⾥选择HTTP。

Auto Connect为当2G/3G/WIFI⽹络开启时,⾃动开启代理服务。不勾选,我们⼿动启动,以获取最⼤灵活性。

Bypass Addresses:相当于⿊名单列表,选择排除代理的IP范围,有需要的可以⾃⼰⼿动设置。

2). 认证信息配置:

Enable Authentication:如果代理服务器需要账户、密码认证,勾选。

User:认证账户名。

Password:认证密码。

NTLM Authentication:NTLM/ NTLM2,Windows早期的⼀种认证⽅式,不⽤勾选。

3). 特征设置:

Global Proxy:⼀定要勾选,即为全局代理,代理所有App

Individual Proxy:单独代理所选App,勾选了第⼀条的不⽤管。

Bypass Mode:勾选了代表第⼆条中所选App不代理,勾选了第⼀条的不⽤管。

DNS Proxy:开启DNS代理。

4). 通知设置:

Ringtone:选择通知铃声。

Vibrate:

5). 都设置完成后,开启Proxy Switch即可。注意:如果使⽤ProxyDroid,⽆需在系统wifi处设置代理。

同理的

使用Burp透明代理模式

我们使用Burp的透明代理模式,在BurpSuite中监听80和443这两个端口,并且将其设置为透明代理模式:

image-20230516153024999

手机连接电脑,以下命令:

adb shell
su
iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to 172.20.10.3:80
iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to 172.20.10.3:443
iptables -t nat -L 查看是否添加成功

逆向检测代理位置,Patch或者HOOK等

Java.perform(function() {

/*
hook list:
1.SSLcontext
2.okhttp
3.webview
4.XUtils
5.httpclientandroidlib
6.JSSE
7.network\_security\_config (android 7.0+)
8.Apache Http client (support partly)
9.OpenSSLSocketImpl
10.TrustKit
11.Cronet
*/

// Attempts to bypass SSL pinning implementations in a number of
// ways. These include implementing a new TrustManager that will
// accept any SSL certificate, overriding OkHTTP v3 check()
// method etc.
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var HostnameVerifier = Java.use('javax.net.ssl.HostnameVerifier');
var SSLContext = Java.use('javax.net.ssl.SSLContext');
var quiet_output = false;

// Helper method to honor the quiet flag.

function quiet_send(data) {

if (quiet_output) {

return;
}

send(data)
}


// Implement a new TrustManager
// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
// Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切换成ART使用.
/*
06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err: at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err: at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
*/
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var TrustManager;
try {
TrustManager = Java.registerClass({
name: 'org.wooyun.TrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() {
// var certs = [X509Certificate.$new()];
// return certs;
return [];
}
}
});
} catch (e) {
quiet_send("registerClass from X509TrustManager >>>>>>>> " + e.message);
}





// Prepare the TrustManagers array to pass to SSLContext.init()
var TrustManagers = [TrustManager.$new()];

try {
// Prepare a Empty SSLFactory
var TLS_SSLContext = SSLContext.getInstance("TLS");
TLS_SSLContext.init(null, TrustManagers, null);
var EmptySSLFactory = TLS_SSLContext.getSocketFactory();
} catch (e) {
quiet_send(e.message);
}

send('Custom, Empty TrustManager ready');

// Get a handle on the init() on the SSLContext class
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');

// Override the init method, specifying our new TrustManager
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {

quiet_send('Overriding SSLContext.init() with the custom TrustManager');

SSLContext_init.call(this, null, TrustManagers, null);
};

/*** okhttp3.x unpinning ***/


// Wrap the logic in a try/catch as not all applications will have
// okhttp as part of the app.
try {

var CertificatePinner = Java.use('okhttp3.CertificatePinner');

quiet_send('OkHTTP 3.x Found');

CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {

quiet_send('OkHTTP 3.x check() called. Not throwing an exception.');
}

} catch (err) {

// If we dont have a ClassNotFoundException exception, raise the
// problem encountered.
if (err.message.indexOf('ClassNotFoundException') === 0) {

throw new Error(err);
}
}

// Appcelerator Titanium PinningTrustManager

// Wrap the logic in a try/catch as not all applications will have
// appcelerator as part of the app.
try {

var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');

send('Appcelerator Titanium Found');

PinningTrustManager.checkServerTrusted.implementation = function() {

quiet_send('Appcelerator checkServerTrusted() called. Not throwing an exception.');
}

} catch (err) {

// If we dont have a ClassNotFoundException exception, raise the
// problem encountered.
if (err.message.indexOf('ClassNotFoundException') === 0) {

throw new Error(err);
}
}

/*** okhttp unpinning ***/


try {
var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
OkHttpClient.setCertificatePinner.implementation = function(certificatePinner) {
// do nothing
quiet_send("OkHttpClient.setCertificatePinner Called!");
return this;
};

// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(p0, p1) {
// do nothing
quiet_send("okhttp Called! [Certificate]");
return;
};
CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(p0, p1) {
// do nothing
quiet_send("okhttp Called! [List]");
return;
};
} catch (e) {
quiet_send("com.squareup.okhttp not found");
}

/*** WebView Hooks ***/

/* frameworks/base/core/java/android/webkit/WebViewClient.java */
/* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
var WebViewClient = Java.use("android.webkit.WebViewClient");

WebViewClient.onReceivedSslError.implementation = function(webView, sslErrorHandler, sslError) {
quiet_send("WebViewClient onReceivedSslError invoke");
//执行proceed方法
sslErrorHandler.proceed();
return;
};

WebViewClient.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c, d) {
quiet_send("WebViewClient onReceivedError invoked");
return;
};

WebViewClient.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function() {
quiet_send("WebViewClient onReceivedError invoked");
return;
};

/*** JSSE Hooks ***/

/* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
/* public final TrustManager[] getTrustManager() */
/* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error */
// var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
// TrustManagerFactory.getTrustManagers.implementation = function(){
// quiet_send("TrustManagerFactory getTrustManagers invoked");
// return TrustManagers;
// }

var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection");
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setDefaultHostnameVerifier(HostnameVerifier) */
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function(hostnameVerifier) {
quiet_send("HttpsURLConnection.setDefaultHostnameVerifier invoked");
return null;
};
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setSSLSocketFactory(SSLSocketFactory) */
HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
return null;
};
/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
/* public void setHostnameVerifier(HostnameVerifier) */
HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
return null;
};

/*** Xutils3.x hooks ***/
//Implement a new HostnameVerifier
var TrustHostnameVerifier;
try {
TrustHostnameVerifier = Java.registerClass({
name: 'org.wooyun.TrustHostnameVerifier',
implements: [HostnameVerifier],
method: {
verify: function(hostname, session) {
return true;
}
}
});

} catch (e) {
//java.lang.ClassNotFoundException: Didn't find class "org.wooyun.TrustHostnameVerifier"
quiet_send("registerClass from hostnameVerifier >>>>>>>> " + e.message);
}

try {
var RequestParams = Java.use('org.xutils.http.RequestParams');
RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
sslSocketFactory = EmptySSLFactory;
return null;
}

RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
hostnameVerifier = TrustHostnameVerifier.$new();
return null;
}

} catch (e) {
quiet_send("Xutils hooks not Found");
}

/*** httpclientandroidlib Hooks ***/
try {
var AbstractVerifier = Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
AbstractVerifier.verify.overload('java.lang.String', '[Ljava.lang.String', '[Ljava.lang.String', 'boolean').implementation = function() {
quiet_send("httpclientandroidlib Hooks");
return null;
}
} catch (e) {
quiet_send("httpclientandroidlib Hooks not found");
}

/***
android 7.0+ network_security_config TrustManagerImpl hook
apache httpclient partly
***/
var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
// try {
// var Arrays = Java.use("java.util.Arrays");
// //apache http client pinning maybe baypass
// //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
// TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
// quiet_send("TrustManagerImpl checkTrusted called");
// //Generics currently result in java.lang.Object
// return Arrays.asList(chain);
// }
//
// } catch (e) {
// quiet_send("TrustManagerImpl checkTrusted nout found");
// }

try {
// Android 7+ TrustManagerImpl
TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
quiet_send("TrustManagerImpl verifyChain called");
// Skip all the logic and just return the chain again :P
//https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
return untrustedChain;
}
} catch (e) {
quiet_send("TrustManagerImpl verifyChain nout found below 7.0");
}
// OpenSSLSocketImpl
try {
var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, authMethod) {
quiet_send('OpenSSLSocketImpl.verifyCertificateChain');
}

quiet_send('OpenSSLSocketImpl pinning')
} catch (err) {
quiet_send('OpenSSLSocketImpl pinner not found');
}
// Trustkit
try {
var Activity = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
Activity.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(str) {
quiet_send('Trustkit.verify1: ' + str);
return true;
};
Activity.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(str) {
quiet_send('Trustkit.verify2: ' + str);
return true;
};

quiet_send('Trustkit pinning')
} catch (err) {
quiet_send('Trustkit pinner not found')
}

try {
//cronet pinner hook
//weibo don't invoke

var netBuilder = Java.use("org.chromium.net.CronetEngine$Builder");

//https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation = function(arg) {

//weibo not invoke
console.log("Enables or disables public key pinning bypass for local trust anchors = " + arg);

//true to enable the bypass, false to disable.
var ret = netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
return ret;
};

netBuilder.addPublicKeyPins.implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
console.log("cronet addPublicKeyPins hostName = " + hostName);

//var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
//this 是调用 addPublicKeyPins 前的对象吗? Yes,CronetEngine.Builder
return this;
};

} catch (err) {
console.log('[-] Cronet pinner not found')
}
});

流量不通过代理

使用ProxyDroid或者Postern

ProxyDroid上文已经介绍Postern配置如下

image-20211220170123027

使用Proxifier进行绕过

此方法适用于被分析应用程序正常运行于模拟器中,整体思路如下:

安卓模拟器 网络进程 --Proxifier代理 --Burpsuite

在Proxifier中添加proxy

image-20211220182951560

对于Mac下的MuMu的配置如下:

image-20211220183120353

对于Mac下的夜神配置如下:

image-20211220191735429

其他模拟器思路类似

使用传输层协议

使用TCP或UDP

解决方法:WireShark

1). tcpdump 是⼀个运⾏在 Linux 平台的可执⾏ ELF ⽂件 https://www.androidtcpdump.com/android-tcpdump/downloads 下载⼆进制⽂件

2). 由于依赖adb,⾸先在 macOS 安装adb,命令如下:

$ brew cask install android-platform-tools

3).拥有设备root权限

$ adb push tcpdump /data/local/tmp
# adb shell
# su
# chmod 755 tcpdump
# ./tcpdump -i any -p -s 0 -w /sdcard/capture.pcap
$ adb pull /sdcard/capture.pcap /pcpath
使⽤Wireshark打开 pcap⽂件
$ wireshark /pcpath/capture.pcap

使用VPN

需要具体分析

通信数据加解密/签名

Frida

Httpdecrypt

httpdecrypt

Hook

Inspeckage

在看源码等待更新

Hook HTTP/HTTPS

使用系统中SSL库

xposed

java
java.androidVersion > 8
ConscryptFileDescriptorSocket
解释:
基于OpenSSLSocketlmpl的实现
路径:
http://aosp.opersys.com/xref/android-10.0.0_r47/xref/external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptFileDescriptorSocket.java#123
理解:
android中openssl的实现,存在数据加解密的读写接口
com.android.org.conscrypt.ConscryptFileDescriptorScoket$SSLOutputStream.write('[B','int','int')
com.android.org.conscrypt.ConscryptFileDescriptorScoket$SSLIntputStream.read('[B','int','int')

java.androidVersion <= 8
    OpenSSLSocketlmpl
        解释:
            OpenSSL实现
        路径:
            http://aosp.opersys.com/xref/android-8.0.0_r51/xref/external/conscrypt/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
        理解:
            android中openssl的实现,存在数据加解密的读写接口

        com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write('[B','int','int')
        com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read('[B','int','int')
native
/system/lib/libssl.so库中的SSL_read()和SSL_write()
说明:
Java中的SSLOutputSteam.write()和SSLInputStream.read()实际上就是对libssl.so库的SSL_write()和SSL_read()包装调用

frida

java
java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B','int','int').implementation
java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B','int','int').implementation
java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream").write.overload('[B','int','int').implementation
java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream").Read.overload('[B','int','int').implementation
native
Interceptor.attach(address['SSL_read'])
Interceptor.attach(address['SSL_write'])

协议使用了自我集成的SSL类库

举出一些常用的例子

1.Chrome

Android L & M libchrome.so
Android N, O & P libmonochrome.so

2.系统内置webview

使用(pm path com.google.android.webview)得到webview的路径

3.浏览器APP的webviewer

OPPO
libheytapwebview.so
华为
libhwwebviewchromium.so
小米
libmiui_chromium.so
vivo
libwebviewchromium_vivo.so

4.基于Chromium的第三方浏览器内核

腾讯系的APP
libmttwebview.so x5内核
阿里系的APP
libwebviewuc.so UC的U4内核
各种小程序
libxwalkcore.so CrossWalk内核

5.微信(使用多内核的APP)

有些APP回使用多个内核,以微信为例,同时使用了X5和CrossWalk内核
x5
com.tencent.mm.tools进程
聊天界面点开的webview、支付里的页面(长按下啦提示使用x5内核的都可以)
CrossWalk
长按下拉未提示x5
com.tencent.mm:toolsmp进程
公众号文章、搜一搜
com.tencent.mm:appbrand进程
小程序

native

1.对于native库,写脚本定位APP自带的SSL_read和SSL_write的偏移量,工程性质问题
https://mabin004.github.io/2020/07/24/自动定位webview中的SLL-read和SSL-write/
https://nytrosecurity.com/2018/02/26/hooking-chromes-ssl-functions/
Author

ol4three

Posted on

2021-12-06

Updated on

2023-05-16

Licensed under


Comments