1. 개요

frida를 이용한 SSL Pinning 방법은 다양하다. frida-server의 경우에는 루팅/탈옥 환경에서 사용하며 이용성이 크기 때문에 많이 쓰이고 있다. 하지만 frida-server는 여러 가지 이유로 상황적 제약을 받는데 이를 타파하기 위해 frida-gadget을 사용하기도 하며 비 루팅/비 탈옥 환경에서 유용하게 쓰이기도 한다. 이번에는 간단한 후킹을 통해 frida-server 활용한 루팅/탈옥 환경에서의 SSL Pinning을 우회하는 방법을 해보자.

 

2. 준비

Frida가 설치된 환경, adb

 

3. 방법

우선 일전에 frida, frida-server설치 방법은 제외하고 진행하도록 하겠습니다.

 

frida는 실행 시에 코드를 주입하여 쓰는 방법을 많이 쓰는데 이는 웹페이지를 직접 연결하여 사용하기도 합니다.

 

frida -U -l ~~.js <hooking 할 어플명>

 

위의 명령어를 통해 진행하면 됩니다.

 

아래의 코드를 이용하여 진행하면 됩니다.

 

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)

*/

 

// 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) {

console.log("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) {

console.log(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');

 

console.log('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

console.log("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

console.log("okhttp Called! [Certificate]");

return;

};

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

// do nothing

console.log("okhttp Called! [List]");

return;

};

} catch (e) {

console.log("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() */

 

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"

console.log("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) {

console.log("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) {

console.log("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) {

// console.log("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) {

console.log("TrustManagerImpl verifyChain nout found below 7.0");

}

// -- Sample Java

//

// "Generic" TrustManager Example

//

// TrustManager[] trustAllCerts = new TrustManager[] {

// new X509TrustManager() {

// public java.security.cert.X509Certificate[] getAcceptedIssuers() {

// return null;

// }

// public void checkClientTrusted(X509Certificate[] certs, String authType) { }

 

// public void checkServerTrusted(X509Certificate[] certs, String authType) { }

 

// }

// };

 

// SSLContext sslcontect = SSLContext.getInstance("TLS");

// sslcontect.init(null, trustAllCerts, null);

 

// OkHTTP 3 Pinning Example

// String hostname = "swapi.co";

// CertificatePinner certificatePinner = new CertificatePinner.Builder()

// .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")

// .build();

 

// OkHttpClient client = new OkHttpClient.Builder()

// .certificatePinner(certificatePinner)

// .build();

 

// Request request = new Request.Builder()

// .url("https://swapi.co/api/people/1")

// .build();

 

// Response response = client.newCall(request).execute();

});

 

frida-gadget도 같은 코드를 이용하여 SSL Pinning이 가능하며 frida-gadget은 따로 페이지를 만들어 설치 방법을 쓰도록 하겠습니다.

 

설명이 길지 않은 것은 이미 일전에 설치방법을 올린 부분이 있어 사용하는 방법을 위주로 올렸습니다.

1. 개요

Frida는 다양한 활용법이 있습니다. 우선 Frida를 이용한 Method 변환 방법을 해보도록 하겠습니다.

 

2. 준비

Frida가 설치된 환경

 

3. 방법

참조 : https://blog.attify.com/bypass-jailbreak-detection-frida-ios-applications/

전부 위의 주소를 참조하여 진행하였습니다.

위의 페이지는 iOS 위주로 설명 중이지만 같은 방법으로 Android에도 적용 가능하며 변형하여 활용할 수도 있습니다.

 

for (var className in ObjC.classes){

if (ObjC.classes.hasOwnProperty(className))

{console.log(className);} }

 

위의 코드는 Class명을 찾는 소스입니다. 위의 소스를 .js파일로 만들어 Frida 실행 시에 소스를 주입하여 실행시키는 형식입니다.

 

frida -U -l ~~.js <어플명> > class.txt

 

페이지에서는 직접 띄우는 형식이거나 grep으로 해당 부분을 찾아내는 형식이었지만 windows환경에서 활용하기에는 파일로 업데이트하여 직접 찾는 것이 훨씬 수월했습니다.

 

console.log("[*] Started: Find All Methods of a Specific Class");

if (ObjC.available) {

try {

var className = "의심되는 Class";

var methods = eval('ObjC.classes.' + className + '.$methods');

for (var i = 0; i < methods.length; i++) {

try { console.log("[-] "+methods[i]); }

catch(err) { console.log("[!] Exception1: " + err.message); }

} }

catch(err) { console.log("[!] Exception2: " + err.message); } }

else { console.log("Objective-C Runtime is not available!"); }

console.log("[*] Completed: Find All Methods of a Specific Class");

 

위의 소스는 의심스러운 ClassMethod를 찾는 소스입니다.

 

위에 있는 의심되는 ClassClass들을 찾은 뒤 그 Class내에 있는 Method들 중 필요한 Method를 변조하기 위한 과정입니다.

위의 소스 또한 .js파일로 만들어 줍니다.

 

frida -U -l ~~.js <어플명> > test.txt

 

전에 말씀드린 것과 같이 windows의 환경을 고려하여 txttxt 파일로 만들어주어 찾기 쉽도록 한 것입니다.

위의 txt 파일에 Method들 중 의심스러운 Method 혹은 확인된 Method를 변조하는 과정을 수행하기 전에 해당 Method의 반환 값을 확인하는 과정을 진행합니다.

 

if (ObjC.available) {

 

try { var className = "의심되는 Class";

var funcName = "의심되는 Method";

var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');

 

Interceptor.attach(hook.implementation, {

onLeave: function(retval) { console.log("[*] Class Name: " + className);

console.log("[*] Method Name: " + funcName);

console.log("\t[-] Type of return value: " + typeof retval);

console.log("\t[-] Return Value: " + retval); } }); }

catch(err) { console.log("[!] Exception2: " + err.message); } }

 

else { console.log("Objective-C Runtime is not available!"); }

 

위의 ClassMethod를 써넣어줍니다.. 여기에서 주의할 점은 Method명을 넣을 때 Method“-” 혹은 “ ”처럼 띄어쓰기 및 빼기 표시는 꼭 같이 넣어주어야 합니다.

 

위처럼 진행할 경우 해당 Method의 반환 값이 True 혹은 False이거나 0x0 혹은 0x1의 값을 확인할 수 있습니다.

 

위와 같은 반환 값 즉 return value를 알게 되었다면 해당 값을 반대 값으로 바꾸어 줍니다. 이 과정을 수행하는 이유는 정상적으로 작동하는 앱에서 만약 문제 발생 시에 차단하는 것을 문제 존재 시 정상적으로 넘어가는 형식으로 바꾸어주는 것이라 생각하시면 되겠습니다.

 

if (ObjC.available) {

 

try {

var className = "의심되는 Class";

var funcName = "의심되는 Method";

var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');

 

Interceptor.attach(hook.implementation, {

onLeave: function(retval) { console.log("[*] Class Name: " + className);

console.log("[*] Method Name: " + funcName);

console.log("\t[-] Type of return value: " + typeof retval);

console.log("\t[-] Original Return Value: " + retval);

newretval = ptr("수정할 값")

retval.replace(newretval)

console.log("\t[-] New Return Value: " + newretval) } }); }

catch(err) { console.log("[!] Exception2: " + err.message); } }

 

else { console.log("Objective-C Runtime is not available!"); }

 

위의 수정할 값에는 0x0일 경우 0x1을 넣어주는 방식으로 진행하시면 됩니다.

 

위의 과정은 한 번에 성공할 확률이 높지 않습니다. 이 과정을 조금 더 세밀하고 정확하게 진행하고 싶으신 분은 ollydbg, IDA와 같은 동적 분석 툴을 이용하여 Method의 동작이나 탐지되는 Class를 분석하여 진행하면 실수를 줄일 수 있습니다.

 

하지만 위와 같은 방법을 반복적으로 진행하여 분석하여도 전혀 문제가 되지 않으며 하나씩 하다 보면 여러 가지 방법을 찾을 수 있을 것입니다.

전에 썼었는데 지워져 버려서 다시 씀.

우선 frida와 frida-gadget은 구동하는 환경에 따라 실행 방식을 변경하는 형식임.

 

frida : frida-server를 device안에서 실행시켜 앱과 연동하는 방식 (루팅 된 환경)

frida-gadget : 별도의 frida-server를 device에서 먼저 실행시키지 않고 lib를 추가하여 앱 실행 시 frida-server가 구동하는 방식 (비 루팅 환경에서도 쓸 수 있음.)

 

1. lib파일 추가

frida-gadget 파일을 다운받아 앱 내부에 삽입.

이름을 'libfrida.so' 와 비슷한 형식으로 바꿔 줌.

 

2. manifest 파일에 permission 추가

<uses-permission android:name="android.permission.INTERNET" />

위에 작성한 코드를 manifest 파일에 추가.

 

3. 앱에서 가장 먼저 실행되는 부분에 코드 추가

우선 먼저 실행되는 smali파일을 찾는 것이 중요함.

adb shell "dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'"

위의 명령어를 앱 실행과 동시에 cmd를 통해 입력하면 현재 실행 중인 뷰가 참조하는 smali파일의 위치를 알 수 있음.

 

const-string v1, "frida"
invoke-static {v1}, Ljava/lang/system;->loadLibrary(Ljava/lang/String;)V

찾은 smali파일에 가장 먼저 코드가 실행될 수 있도록 위의 코드를 추가.

 

*파일 이름과 코드 내 명칭은 통일되어야 함*

 

4. 앱 리패키징

apktool b -o [out] [decompiled folder]

위의 apktool을 이용하여 해도 괜찮지만 apk easy tool을 이용하는 방법도 괜찮음. (블로그 내 포스팅된 글 참조)

 

5. frida 실행

이전에 frida관련 글을 보고 실행시켜 줘도 되고 frida를 실행시켜 코드를 수정할 경우 아래의 명령어를 실행시켜주면 됨.

frida -U gadget  

 

끝!

1. 개요

인증서 설치만으로 SSL Pinning우회가 안될 경우 이를 우회하는 방법은 여러 가지가 있지만 간단히 앱을 설치하는 것만으로 우회하는 방법도 있다.

 

2. 준비

Xposedinstaller.apk(버전은 알아서), Just trust me.apk(모듈 버전도 알아서), Nox

 

3. 설치 방법

우선 어플을 먼저 설치해줍니다.

 

설치 후 프레임워크를 설치해야 하는데 이 과정은 디바이스가 루팅이 되어있는 상태에서 설치가 가능합니다. 그러므로 Nox를 루팅 상태로 만든 후에 시도하여 줍니다.

위의 XPOSED 프레임워크 설치 및 업데이트 밑의 VERSION 89를 눌러주면 위와 같은 창이 뜨는데 Install을 눌러 설치를 해줍니다.(이때 디바이스는 루팅 된 상태여야 합니다.)

위와 같이 진행되면 슈퍼 루트의 권한을 주어서 진행합니다.

설치 후 재시작하여 줍니다.

 

이렇게 설치가 정상적으로 되었다면 Just trust me.apk를 설치해줍니다.

 

위와 같이 모듈을 설치한 후 체크해주면 사용할 준비가 된 것인데 이때 Just trust me설치 후에 디바이스를 재부팅해주어야 모듈이 정상적으로 동작합니다.

 

이 과정을 거친 후에는 이전의 인증서 설치를 이용항 SSL Pinning과 같은 방식으로 진행하시면 됩니다.

 

4. 제거 방법

제거할 때는 모듈을 길게 눌러주면 위와 같은 창이 뜹니다. 모듈은 이렇게 제거해주시면 됩니다.

제거에는 순서가 있는데 설치할 때 받은 파일을 먼저 지워주고 아래의 SPOSED 프레임워크 제거를 해주어야 합니다.

먼저 설치에서 Delete downloaded file을 해줍니다.

그런 후 Uninstaller에서 Sposed 프레임워크 제거를 해주면 제거가 됩니다.

프레임워크를 지우는 과정은 설치 때와 같이 재시작이 필요합니다.

돌아와서 Uninstaller의 Delete downloaded file을 진행한 후 어플도 지워주시면 됩니다.

+ Recent posts