您好,登錄后才能下訂單哦!
如何使用Frida繞過Android網絡安全配置,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
我們將演示如何利用Frida腳本來繞過Android的網絡安全配置,這是一種繞過網絡安全配置的新技術。除此之外,我們還將演示如何在其他場景來測試該腳本,并分析腳本的運行機制。
在之前的一次Android應用程序安全審計過程中,首先我們要做的就是準備滲透測試的環境,并配置應用程序來繞過網絡安全配置。由于我個人比較喜歡Frida,因此它也就成為了我的首選工具。
當時我下載了兩到三個腳本,但是當我在Android 7.1.0中運行腳本時,沒有一個可以成功的。這也就是為什么我想研究網絡安全配置的運行機制,并且如何用Frida繞過它們。
我所做的第一件事就是生成不同的測試用例,我嘗試選擇了幾款比較常見的:
1、OKHttp
2、HttpsURLConnection
3、WebView
接下來,我用不同的網絡安全配置生成了三個應用程序:
1、一個使用了默認NSC配置的應用程序-BypassNSC
2、一個帶有NSC文件(僅使用了系統證書)的應用程序-BypassNSC2
3、一個帶有NSC文件的應用程序(強制證書綁定)-BypassNSC3
代碼會解析并驗證Android SDK中的網絡安全配置,我的測試版本為24、25和26。廣大用戶可以點擊【這里】獲取我所生成的應用程序以及所使用的腳本。
腳本名如下:
network-security-config-bypass-1.js
network-security-config-bypass-2.js
network-security-config-bypass-3.js
network-security-config-bypass-cr.js
下圖為每一個腳本的分析測試結果:
原始引用:【鏈接】
該腳本會修改NetworkSecurityConfig.Builder類中的getEffectiveCertificatesEntryRefs方法,該方法可以返回有效證書列表。在標準的Android配置中,它所返回的有效證書列表就是安裝在目標系統中的有效證書。毫無以為,這個腳本將直接返回用戶安裝的證書,因此理論上來說,它可以繞過前兩個應用程序的網絡安全配置,但讓我驚訝的是,它竟然也適用于第三種情況,也就是證書綁定配置。我們可以使用下列方法來驗證綁定的證書:
android.security.net.config.NetworkSecurityTrustManager.checkPins
下面的棧跟蹤記錄顯示了代碼到checkPins函數的執行路徑:
at android.security.net.config.NetworkSecurityTrustManager.checkPins(Native Method) at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:95) at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88) at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:178) at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:596) at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357) ...
如果補丁沒有執行,那么當執行路徑抵達該函數時,則會拋出異常:
Caused by: java.security.cert.CertificateException: Pin verification failed at android.security.net.config.NetworkSecurityTrustManager.checkPins(NetworkSecurityTrustManager.java:148) at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:95) at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88) at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:178) at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:596) at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) at com.android.org.
我們看一下這個方法的實現代碼(API 25):
private void checkPins(List<X509Certificate> chain) throws CertificateException { PinSet pinSet = mNetworkSecurityConfig.getPins(); if (pinSet.pins.isEmpty() || System.currentTimeMillis() > pinSet.expirationTime || !isPinningEnforced(chain)) { return; } Set<String> pinAlgorithms = pinSet.getPinAlgorithms(); Map<String, MessageDigest> digestMap = new ArrayMap<String, MessageDigest>( pinAlgorithms.size()); for (int i = chain.size() - 1; i >= 0 ; i--) { X509Certificate cert = chain.get(i); byte[] encodedSPKI = cert.getPublicKey().getEncoded(); for (String algorithm : pinAlgorithms) { MessageDigest md = digestMap.get(algorithm); if (md == null) { try { md = MessageDigest.getInstance(algorithm); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } digestMap.put(algorithm, md); } if (pinSet.pins.contains(new Pin(algorithm, md.digest(encodedSPKI)))) { return; } } } // TODO: Throw a subclass of CertificateException which indicates a pinning failure. throw new CertificateException("Pin verification failed"); }
這個方法可以接收網站通信所返回的證書列表,它做的第一件事情就是條件檢測:
1、pinset為空
2、驗證時pinset已過期
3、證書綁定并非配置強制要求
如果上述條件沒有一個為真,綁定驗證將會被忽略。如果必須實現驗證,應用程序將檢查站點提供的任何證書是否與網絡安全配置文件中定義的某個證書匹配,此時驗證就是成功的。如果沒有發生這種情況,該方法將拋出前一個stacktrace中顯示的異常。
一開始,我以為這個問題存在于用來分析每一個證書的for循環中,所以我在Frida腳本中添加了下列log:
var Pin = Java.use("android.security.net.config.Pin");Pin.$init.implementation = function (digestAlg, digest) {var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());console.log("\nBacktrace:\n" + bt);console.log(digestAlg);return this.$init(digestAlg,digest);}
它可以輸出驗證過程中的每一個pin,當我運行改動過的應用程序之后,我發現這并沒有什么用。于是我又在log中添加了針對pinSet.getPinAlgorithms()的調用,并在for循環之前執行:
var PinSet = Java.use("android.security.net.config.PinSet");PinSet.getPinAlgorithms.implementation = function () {var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());console.log("\nBacktrace:\n" + bt);return this.getPinAlgorithms();}
這一次什么都沒打印出來,于是接下來我得看看函數的條件判斷是否為真,因此我在腳本中添加了下列代碼:
NetworkSecurityTrustManager.checkPins.implementation = function (pins) {var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());console.log("\nBacktrace:\n" + bt);pinSet = this.mNetworkSecurityConfig.value.getPins();console.log("pinSet.pins.value.isEmpty: " +pinSet.pins.value.isEmpty());console.log("isPinningEnforced: " +this.isPinningEnforced(pins));console.log("pins.isEmpty: " +pins.isEmpty());console.log(System.currentTimeMillis())console.log(pinSet.expirationTime.value);console.log(System.currentTimeMillis() > pinSet.expirationTime.value);this.checkPins(pins);}
運行應用程序之后,我拿到了下列輸出:
pinSet.pins.value.isEmpty: falseisPinningEnforced: false <-- this condition is the problematic onepins.isEmpty: false15620312482749223372036854775807false
我們可以看到,isPinningEnforced為False,此時其他所有的表達式都將為True。該方法的實現代碼如下:
private boolean isPinningEnforced(List<X509Certificate> chain) throws CertificateException {if (chain.isEmpty()) {return false;}X509Certificate anchorCert = chain.get(chain.size() - 1);TrustAnchor chainAnchor =mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);if (chainAnchor == null) {throw new CertificateException("Trusted chain does not end in a TrustAnchor");}return !chainAnchor.overridesPins;}
原來,問題出在findTrustAnchorBySubjectAndPublicKey的身上,它是NetworkSecurityConfig類中的一個方法,能夠返回一個chainAnchor:
public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {for (CertificatesEntryRef ref : mCertificatesEntryRefs) {TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);if (anchor != null) {return anchor;}}return null;}
它會在配置過程中對創建的CertificatesEntryRef進行迭代,并返回第一個跟SubjectAndPublicKey匹配的對象。在這個場景中,它將返回的是其中一個代理。研究完源代碼后,我找到了CertificatesEntryRef類,并發現了唯一一個構造器:
public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {mSource = source;mOverridesPins = overridesPins;}
如果再回頭看一次Frida腳本,你將會發現CertificatesEntryRef是以下列方式創建的:
NetworkSecurityConfig_Builder.getEffectiveCertificatesEntryRefs.implementation = function(){origin = this.getEffectiveCertificatesEntryRefs()source = UserCertificateSource.getInstance()userCert = CertificatesEntryRef.$new(source,true) <-- sets overridesPins in trueorigin.add(userCert)return origin}
這也就是為什么這個腳本適用于所有場景。
原始引用:【鏈接】
在這個場景下,唯一適用的就是不包含網絡安全配置文件的應用程序。
我分析了一下為什么補丁不起作用,原因在于parseNetworkSecurityConfig方法:
XmlUtils.beginDocument(parser, "network-security-config");int outerDepth = parser.getDepth();while (XmlUtils.nextElementWithin(parser, outerDepth)) {//here it creates a NetworkSecurityconfig.Builder based on the xml structure....}...NetworkSecurityConfig.Builder platformDefaultBuilder =NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion); <-- this is the method changed with the scriptaddDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);//baseConfigBuilder is null only if the xml network-security-config is not defined in the AndroidManifest.xmlif (baseConfigBuilder != null) {baseConfigBuilder.setParent(platformDefaultBuilder);addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder);} else {baseConfigBuilder = platformDefaultBuilder;}...mDefaultConfig = baseConfigBuilder.build();mDomainMap = configs;}
構建方法會生成NetworkSecurityConfig實體:
public NetworkSecurityConfig build() {boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted();boolean hstsEnforced = getEffectiveHstsEnforced();PinSet pinSet = getEffectivePinSet();List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs();return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs);}
有效證書源在entryRefs變量中定義,其構造方法如下:
private List<CertificatesEntryRef> getEffectiveCertificatesEntryRefs() {if (mCertificatesEntryRefs != null) {return mCertificatesEntryRefs;}if (mParentBuilder != null) {return mParentBuilder.getEffectiveCertificatesEntryRefs();}return Collections.<CertificatesEntryRef>emptyList();}
此時,mCertificatesEntryRefs不為空,并會返回標準的SystemCertificateSource。因此,mParentBuilder永遠不會被調用。
接下來,當服務器證書驗證成功后,應用程序將會調用NetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey方法,該方法將會對有效證書進行過濾:
public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {for (CertificatesEntryRef ref : mCertificatesEntryRefs) {TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);if (anchor != null) {return anchor;}}return null;}
并導致棧跟蹤拋出異常:
com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:375)at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:304)at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)...
原始引用:【鏈接】
該腳本適用于三個場景中的其中兩個,因為補丁會在驗證證書的方法中執行。但是它不適用于第三種場景,因為證書綁定是在其他方法中執行的,具體可以參考棧跟蹤記錄中的錯誤信息:
at android.security.net.config.NetworkSecurityTrustManager.checkPins(NetworkSecurityTrustManager.java:148)at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:95)at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:88)at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:203)at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:592)at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:351)... 25 more
在這個場景下,修補的方法為getConfigSource,當代碼對network-security-config進行解析時將會調用這個方法。我們可以看到,重寫的方法會創建一個DefaultConfigSource,并在Android 23中以參數形式進行定義。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。