당분간 모바일 해킹을 할 시간이 부족해 당장 기억나는 부분들로만 정리한 내용 추가 예정

Android 구조 및 동작이해

Android 진단 환경 구축

ADB

SDK 설치 (Android Studio 설치하면 알아서 깔림)

For Mac 터미널에서 code ~/.zshrc 입력 후 아래와 같이 환경 변수 등록

ADB install Fail

NSTALL_FAILED_VERIFICATION_FAILURE 문제 구글 플레이 서비스의 apk 정책 때문에 발생하는 문제로 아래 코드 입력 후 다시 설치시 됨.

adb shell settings put global verifier_verify_adb_installs 0

KeyStore

Android Studio

[File -> New Project -> Build] 메뉴에서 아래와 같이 진행

KeyTool

keytool -genkey -v -keystore dogyun.keystore -alias dogyun -keyalg RSA -keysize 2048 -validity 10000 명령어 입력 후 생성

Proxy

낮은 버전의 경우

단순히 127.0.0.1:burp_port 들어가서 인증서 다운로드 후 Astro에서 인증서 설치 진행

높은 버전의 경우

  1. Burp Suite [Proxy] - [Options] - [Import/export certificate] 기능을 통해 인증서 파일을 추출
  2. openssl x509 -inform DER -in cacert.cer -out cacert.pem pem 변환
  3. openssl x509 -inform PEM -subject_hash_old -in cacert.pem 해쉬값 추출
  4. mv cacert.pem 9a5ba575.0 파일명 해쉬값으로 변경
  5. adb push 9a5ba575.0 /data/local/tmp
  6. mount -o rw,remount / 쓰기권한 마운트 시키기
  7. cp 9a5ba575.0 /system/etc/security/cacerts/
    1. ls -l /system/etc/security/cacerts/ | grep 9a5ba575
  8. mount -o ro,remount /
  9. [설정] - [생체 인식 및 보안] - [기타 보안 설정] - [인증서 확인] 에서 확인

정적 분석

APK 추출

  1. ASTRO 백업 기능 이용
  2. 플레이스토어에서 APK 다운로드
  3. APK Extractor 이용

디컴파일

  1. jadx APK 파일 그냥 jadx에 넣으면 됨
  2. APK Tool 이용 https://github.com/iBotPeaches/Apktool/releases/tag/v2.8.0
    java -jar apktool_2.8.0.jar d "디컴파일 할 앱 이름"
    

  3. dex2jar 이용 https://github.com/pxb1988/dex2jar/releases/tag/v2.1
    # Windows의 경우 bat 파일로 돌리면 됨
    ./d2j-dex2jar.sh apk
    

Android Manifest 분석

Activity

선언된 Activity의 exported 속성 확인

  • false - 동일 어플 및 uid를 가진 앱에서만 실행 가능
  • true - 다른 앱에서 실행 가능
# am
adb shell am start -n 패키지명/.Activity 경로

# drozer
run app.activity.start --component 패키지명 패키지명.Activity명

Receiver

선언된 Receiver의 exported 속성 확인

  • true - 다른 애플리케이션에서 intent 가능
# am
adb shell am broadcast -a Action_name -n 패키지명/.Receiver 경로

# drozer
## Drozer의 attacksurface 기능을 사용하여 어플 내 공격 가능성 있는 content 검색
run app.broadcast.send --component 패키지명 Receiver경로
## Drozer의 기능을 사용하여 Provider에 존재하는 content 경로 정보 획득
run [app.provider.info](http://app.provider.info/) -a 패키지명
## 보다 자세하게 Provider에 대한 모든 URI 검색
run scanner.provider.finduris -a 패키지명
## Drozer로 확인한 content provider를 사용하여 데이터 확인
run app.provider.query content경로

Provider

선언된 Provider의 exported 속성 확인

  • false - 동일 어플, uid에서만 실행 가능
  • true - 다른 어플에서 provider 실행 가능
# am
adb shell content query —uri content경로

# drozer
run app.package.attacksurface 패키지명

정보 노출

소스코드 정보 노출

단순히 APK 코드 내 중요 정보가 하드코딩 되어 있는 경우

단말기 내 사용자 정보 저장

# 아래 폴더 파일 보기
/data/data/app_identify/shared_prefs
/data/data/app_identify/database/ids2
/data/data/app_identify

메모리 내 중요 정보 노출

백그라운드 화면 정보 노출

디버그 로그에서 정보 노출

네트워크에서 정보 노출

동적 분석

Python3 Android.py --type 1 --app Test --script hook.js

import argparse
import frida
import sys
import time

from pynput import keyboard


def on_message(message, data):
    if message['type'] == 'send':
        print(message['payload'])
    elif message['type'] == 'error':
        print(message['stack'])

def get_messages_from_js(message, data):
    print(message['payload'])

def get_script(script_name):
    with open("./"+script_name, 'r') as f:
        script = f.read()
    return script

help_script = """
HELP
"""

parser = argparse.ArgumentParser(description=help_script)
parser.add_argument('--script', required=True, help='JS File to Inject')
parser.add_argument('--app', required=True, help='JS File to Inject')
parser.add_argument('--type', required=False, help='JS File to Inject')
args = parser.parse_args()

# 대상 앱의 패키지 이름 또는 식별값
target_app_identifier = args.app.split('/')[0]
target_app_name = args.app.split('/')[1]
target_script = args.script

# Frida 디바이스에 연결
device = frida.get_usb_device()

# 스크립트 로드 함수
def load_script(target_script):
    global process
    global waiting_for_key
    # 스크립트 로드
    script = process.create_script(get_script(target_script))
    script.on('message', on_message)
    script.load()
    
    time.sleep(3)
    waiting_for_key = False

# 앱이 이미 실행 중인지 확인하는 함수
def is_app_running(app_identifier):
    processes = device.enumerate_processes()
    for process in processes:
        # if process.name == "카카오페이" or process.name == "com.kakaopay.app":
        #     print(process.name)
        if process.name == app_identifier:
            return process.pid
    return False

# 앱이 이미 실행 중인 경우 중지하고 spawn하는 함수
def spawn_app(app_identifier, app_name):
    global process
    isRun = is_app_running(app_name)
    if isRun:
        print("앱이 이미 실행 중입니다. 중지시킵니다.")
        device.kill(isRun)
        time.sleep(2)  # 2초 동안 앱 초기화 대기
    
    print("앱을 spawn합니다.")
    pid = device.spawn([app_identifier], timeout=10)
    time.sleep(2)  # 2초 동안 앱 초기화 대기
    process = device.attach(pid)

    # Process resume (실행시킴.)
    device.resume(pid)

    # 스크립트 로드
    load_script(target_script)

    sys.stdin.read()

# 앱 attach 하기
def attach_app(app_identifier):
    global process
    pid = is_app_running(app_identifier)
    print("pid : ", pid, "에 붙습니다.")
    process = device.attach(pid)
        
    # 스크립트 로드
    
    load_script(target_script)

    sys.stdin.read()

### 메인 함수 ###
# 메인 함수에서 비동기 이벤트 루프 실행
def main():
    frida_type = args.type

    if frida_type == '1' or frida_type == 'spawn':
        spawn_app(target_app_identifier, target_app_name)
    elif frida_type == '2' or frida_type == 'attach':
        attach_app(target_app_identifier)
    else:
        print('Injection Type error: ' + frida_type)

if __name__ == "__main__":
    main()

메모리 관련

fridump

# string 뽑기
Python3 fridump3.py -u pid -s

AM

  1. Manifest에서 debugging이 되는지 확인
  2. App pid 정보 확인 (ps | grep diva) 및 am dump

실시간 메모리 후킹

function memoryScan() {
    try {
	  var search_string = ['']; // 찾을 패턴의 문자열 Find string ex) ['a'] Or ['a','b']
        var Modify_string = ['']; // 변조할 문자열 Modify string ex) ['a'] Or ['a','b']
        var patched = false;
		
        search_string.forEach(function (patt, index) {
            var pattern = patt
                .split('')
                .map(char => char.charCodeAt(0).toString(16))
                .join(' ');
			
            Process.enumerateRanges('rw-', {
                onMatch: function (range) {

                    var result = Memory.scanSync(range.base, range.size, pattern); // 패턴 직전 메모리를 원하면 4번째 인자 추가
                    if (result.length > 0) {
					
                        result.forEach(function (match) {
							console.log("");
					console.log("\x1b[31m" + '[*] Scan String: ' + patt + "\x1b[0m");
                            console.log("\x1b[36m" + '[*] String Address: ' + match.address + "\x1b[0m");									                     
                            console.log(hexdump(match.address, { offset: 0, length: 128 }));
                            var mempatch = Modify_string[index];

                            var hexData = [];
                            for (var i = 0; i < mempatch.length; i++) {
                                var hexChar = '0x' + mempatch.charCodeAt(i).toString(16);
                                hexData.push(hexChar);
                            }

                            var nullBytesToAdd = patt.length - mempatch.length;

                            for (var i = 0; i < nullBytesToAdd; i++) {
                                hexData.push('0x00');
                            }

                            Memory.writeByteArray(match.address, hexData);
                            console.log("\x1b[33m" + '[*] Patch Address: ' + match.address + "\x1b[0m");
                            console.log("\x1b[32m[*] Before Patch: " + patt + '   \x1b[0m------>  ' + "\x1b[32mAfter Patch: " + mempatch + "\x1b[0m");
                            console.log(hexdump(match.address, { offset: 0, length: 32 }));
                            patched = true; 
                        });
                    }
                },
                onComplete: function () {}
            });
        });
    } catch (e) {
    }
}
setInterval(memory, 1000);

Trace

Function Trace

function trace(pattern)
{
	var type = (pattern.toString().indexOf("!") === -1) ? "java" : "module";

	if (type === "module") {

		// trace Module
		var res = new ApiResolver("module");
		var matches = res.enumerateMatchesSync(pattern);
		var targets = uniqBy(matches, JSON.stringify);
		targets.forEach(function(target) {
			traceModule(target.address, target.name);
		});

	} else if (type === "java") {

		// trace Java Class
		var found = false;
		Java.enumerateLoadedClasses({
			onMatch: function(aClass) {
				if (pattern.test(aClass)) {
					found = true;
					// console.log(aClass)
					// var className = aClass.match(/[L](.*);/)[1].replace(/\//g, ".");
					if(aClass.includes("."))traceClass(aClass);
				}
			},
			onComplete: function() {}
		});

		// trace Java Method
		if (!found) {
			try {
				traceMethod(pattern);
			}
			catch(err) { // catch non existing classes/methods
				console.error(err);
			}
		}
	}
}

// find and trace all methods declared in a Java Class
function traceClass(targetClass)
{
	var hook = Java.use(targetClass);
	var methods = hook.class.getDeclaredMethods();
	hook.$dispose;

	var parsedMethods = [];
	methods.forEach(function(method) {
		// console.log(method)
		parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
	});

	var targets = uniqBy(parsedMethods, JSON.stringify);
    // console.log("\n[*] " + targetClass)
	targets.forEach(function(targetMethod) {
        // console.log("\t[*] " + targetMethod)
		if(!targetMethod.includes("getGidForName")&&!targetMethod.includes("myPid"))traceMethod(targetClass + "." + targetMethod);
	});
}

// trace a specific Java Method
function traceMethod(targetClassMethod)
{
	var delim = targetClassMethod.lastIndexOf(".");
	if (delim === -1) return;

	var targetClass = targetClassMethod.slice(0, delim)
	var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length)

	var hook = Java.use(targetClass);
	var overloadCount = 0;
    try {
        overloadCount = hook[targetMethod].overloads.length;
    } catch (error) {
        overloadCount = 0;
    }

	console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");

	for (var i = 0; i < overloadCount; i++) {
        hook[targetMethod].overloads[i].implementation = function() {
            console.warn("\n*** entered " + targetClassMethod);
            // console.log(hook.$new().label.value)
            // print backtrace
            // Java.perform(function() {
            //	var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
            //	console.log("\nBacktrace:\n" + bt);
            // });   

            // print args
            if (arguments.length) console.log();
            for (var j = 0; j < arguments.length; j++) {
                // if(typeof(arguments[j])=="object"){
                // 	// console.log(Java.cast(arguments[j].value, Java.use("java.util.List")));
                // 	console.log("arg[" + j + "]: " + JSON.stringify(arguments[j]));
                // 	// console.log(arguments[j].$className)
                // 	// if(arguments[j].$className=="b.f4sQ"){
                // 	// 	console.log(arguments[j].toString())
                // 	// }
                // }else{
                // 	console.log("arg[" + j + "]: " + arguments[j].value);
                // }
                console.log("arg[" + j + "]: " + arguments[j]);
            }

            // print retval
            // var retval = this[targetMethod].apply(this, arguments);
            if(/blockingGetToken/.test(targetMethod)){
                // retval='cyP0v_0qRAqLtt5N11szK2:APA91bGGUKTmB0YADn-QOIjyoLJV1o1ZxB5PNQ7bxMhqc-rkP0ADx-gYr3d7vbfW_6-zs6THyUWDvQzTmxoYJwTgJzWFwsZDPf3xI5evZHzRVgL0pOo0LYKWYpRgeSxxl4slPehpmJoV'
            }
         // rare crash (Frida bug?)
            // if(typeof(retval)=="object"){
            // 	console.log("\nretval: " + JSON.stringify(retval));
            // }else{
            // 	console.log("\nretval: " + retval);
            // }
            // console.log("\nretval: " + retval);
            console.warn("\n*** exiting " + targetClassMethod);
            // return retval;
        }
	}
}

// trace Module functions
function traceModule(impl, name)
{
	console.log("Tracing " + name);

	Interceptor.attach(impl, {

		onEnter: function(args) {

			// debug only the intended calls
			this.flag = false;
			// var filename = Memory.readCString(ptr(args[0]));
			// if (filename.indexOf("XYZ") === -1 && filename.indexOf("ZYX") === -1) // exclusion list
			// if (filename.indexOf("my.interesting.file") !== -1) // inclusion list
				this.flag = true;

			if (this.flag) {
				console.warn("\n*** entered " + name);

				// print backtrace
				console.log("\nBacktrace:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE)
						.map(DebugSymbol.fromAddress).join("\n"));
			}
		},

		onLeave: function(retval) {

			if (this.flag) {
				// print retval
				console.log("\nretval: " + retval);
				console.warn("\n*** exiting " + name);
			}
		}

	});
}

// remove duplicates from array
function uniqBy(array, key)
{
        var seen = {};
        return array.filter(function(item) {
                var k = key(item);
                return seen.hasOwnProperty(k) ? false : (seen[k] = true);
        });
}

// ctrace("/com.test.test.class*/")
function ctrace(pattern){
    setTimeout(function() {
        Java.perform(function () {
            trace(pattern)
        })
    },0)
}

Stack Trace

function arrange(stack) {
	var ret = "";
	for(var i = 0; i < stack.length; ++i)
		ret += stack[i].toString() + "\n";  
	
	return ret;
}

Java.perform(function() {
	var Thread = Java.use("java.lang.Thread");
	var ThreadInstance = Thread.$new();
	var stack = ThreadInstance.currentThread().getStackTrace();
	
	YourClass.Method.implementation = function() {
		var full_call_stack = arrange(stack);
		console.log("Call Stack is : " + full_call_stack); 
	}
})
var ThreadDef = Java.use('java.lang.Thread');
var ThreadObj = ThreadDef.$new();
function stackTrace() {
    var stack = ThreadObj.currentThread().getStackTrace();
    for (var i = 0; i < stack.length; i++) {
        console.log(i + " => " + stack[i].toString());
    }
    console.log("-------------------------------------");
}

Java.perform(function() {
	YourClass.Method.implementation = function() {
		stackTrace();
	}
})

ClassLoader

Basic

Java.perform(function() {
	for(var i=0;i<Java.enumerateClassLoadersSync().length;i++){
		var classLoaderToUse = Java.enumerateClassLoadersSync()[i];
		console.log(`[${i+1}] ${classLoaderToUse}`)
		Java.classFactory.loader = classLoaderToUse;
		try{
			// Hooking Code
		}catch (error){
		}
	}
})

InMemoryDexDump

function InMemoryDexDump(){
    console.log("[*] In Memory Dex Dump v0.1 - @cryptax");
    var count = 0
    Java.perform(function() {
        var memoryclassLoader = Java.use("dalvik.system.InMemoryDexClassLoader");
        memoryclassLoader.$init.overload('java.nio.ByteBuffer', 'java.lang.ClassLoader').implementation = function(dexbuffer, loader) {
            console.log("[*] Hooking InMemoryDexClassLoader");
            count += 1
            var object = this.$init(dexbuffer, loader);
            /* dexbuffer is a Java ByteBuffer
               you cannot dump to /sdcard unless the app has rights to
             */
            var remaining = dexbuffer.remaining();
            const filename = `/data/data/com.dunamu.ustockplus.android/dump${count}.dex`;
            
            console.log("[*] Opening file name=" + filename + " to write " + remaining + " bytes");
            const f = new File(filename, 'wb');
            var buf = new Uint8Array(remaining);
            for (var i = 0; i < remaining; i++) {
                buf[i] = dexbuffer.get();
                //debug: console.log("buf["+i+"]="+buf[i]);
            }
            console.log("[*] Writing " + remaining + " bytes...");
            f.write(buf);
            f.close();
            // checking
            remaining = dexbuffer.remaining();
            if (remaining > 0) {
                console.log("[-] Error: There are " + remaining + " remaining bytes!");
            } else {
                console.log("[+] Dex dumped successfully in " + filename);
            }

            return object;
        }
    });
}

SSL Pinning

Basic

function SSLPinning(){
    /**@@@+++@@@@******************************************************************
     **
    ** Android SSL Pinning frida script v1.0 hyugogirubato
    **
    ** frida -D "DEVICE" -l "pinning.js" -f "PACKAGE"
    **
    ** Update: Dynamic error support.
    **
    ***@@@---@@@@******************************************************************
    */

    // Custom params
    const MODE = {
        SSLPeerUnverifiedException: true,
        HttpsURLConnection: true,
        SSLContext: true,
        TrustManagerImpl: true,
        OkHTTPv3: true,
        Trustkit: true,
        TitaniumPinningTrustManager: true,
        FabricPinningTrustManager: true,
        ConscryptOpenSSLSocketImpl: true,
        ConscryptOpenSSLEngineSocketImpl: true,
        ApacheOpenSSLSocketImpl: true,
        PhoneGapsslCertificateChecker: true,
        IBMMobileFirst: true,
        IBMWorkLight: true,
        ConscryptCertPinManager: true,
        NetsecurityCertPinManager: true,
        AndroidgapWorkLight: true,
        NettyFingerprintTrustManagerFactory: true,
        SquareupCertificatePinner: true,
        SquareupOkHostnameVerifier: true,
        AndroidWebViewClient: true,
        ApacheWebViewClient: true,
        BoyeAbstractVerifier: true,
        ApacheAbstractVerifier: true,
        Appmattus: true,
        ChromiumCronet: true,
        Flutter: true
    };


    let index = 0; // color index
    const COLORS = {
        red: '\x1b[31m',
        green: '\x1b[32m',
        yellow: '\x1b[33m',
        blue: '\x1b[34m',
        magenta: '\x1b[35m',
        cyan: '\x1b[36m',
        reset: '\x1b[0m'
    };

    const randomColor = () => {
        const colorKeys = Object.keys(COLORS).filter(key => key !== "reset" && key !== "red");
        index = (index + 1) % colorKeys.length;
        return COLORS[colorKeys[index]];
    }


    const rudimentaryFix = (typeName) => {
        if (typeName === "boolean") {
            return true;
        } else if (typeName !== "void") {
            return null;
        }
    }

    const loadJava = (library) => {
        try {
            return Java.use(library);
        } catch (e) {
            return undefined;
        }
    }


    setTimeout(function () {
        console.log("---");
        console.log("Capturing Android app...");
        if (Java.available) {
            console.log("[*] Java available");
            Java.perform(function () {

                    if (MODE.SSLPeerUnverifiedException) {
                        const colorKey = randomColor();
                        try {
                            const UnverifiedCertError = Java.use("javax.net.ssl.SSLPeerUnverifiedException");
                            UnverifiedCertError.$init.implementation = function (str) {
                                console.log(`${COLORS.red}[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...${COLORS.reset}`);

                                let className, methodName, callingMethod, returnTypeName;
                                try {
                                    const stackTrace = Java.use("java.lang.Thread").currentThread().getStackTrace();
                                    const exceptionStackIndex = stackTrace.findIndex(stack =>
                                        stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException"
                                    );

                                    if (exceptionStackIndex === -1) {
                                        console.log(`${COLORS.yellow}[-] SSLPeerUnverifiedException not found in the stack trace.${COLORS.reset}`);
                                        return this.$init(str);
                                    }

                                    // Retrieve the method raising the SSLPeerUnverifiedException
                                    const callingFunctionStack = stackTrace[exceptionStackIndex + 1];
                                    className = callingFunctionStack.getClassName();
                                    methodName = callingFunctionStack.getMethodName();
                                    const callingClass = Java.use(className);
                                    callingMethod = callingClass[methodName];

                                    // Skip it when already patched by Frida
                                    if (callingMethod.implementation) {
                                        return;
                                    }

                                    // Trying to patch the uncommon SSL Pinning method via implementation
                                    returnTypeName = callingMethod.returnType.type;
                                    callingMethod.implementation = function () {
                                        rudimentaryFix(returnTypeName);
                                    };
                                    console.log(`${colorKey}  --> SSLPeerUnverifiedException [${className}.${methodName}]${COLORS.reset}`);
                                } catch (e) {
                                    // Dynamic patching via implementation does not works, then trying via function overloading
                                    console.log(`${COLORS.red}[!] The uncommon SSL Pinning method has more than one overload${COLORS.reset}`);
                                    if (String(e).includes(".overload")) {
                                        const splittedList = String(e).split(".overload");
                                        for (let i = 2; i < splittedList.length; i++) {
                                            const extractedOverload = splittedList[i].trim().split("(")[1].slice(0, -1).replaceAll("'", "");
                                            // Check if extractedOverload has multiple arguments
                                            if (extractedOverload.includes(",")) {
                                                // Go here if overloaded method has multiple arguments (NOTE: max 6 args are covered here)
                                                const argList = extractedOverload.split(", ");

                                                // Overload the method based on the number of arguments
                                                callingMethod.overload(...argList).implementation = function (...args) {
                                                    rudimentaryFix(returnTypeName);
                                                };
                                                // Go here if overloaded method has a single argument
                                            } else {
                                                callingMethod.overload(extractedOverload).implementation = function (a) {
                                                    rudimentaryFix(returnTypeName);
                                                };
                                            }
                                        }
                                        console.log(`${colorKey}  --> SSLPeerUnverifiedException [${className}.${methodName}]${COLORS.reset}`);
                                    } else {
                                        console.log(`${COLORS.red}[!] Failed to dynamically patch SSLPeerUnverifiedException ${e}${COLORS.reset}`);
                                    }
                                }
                                return this.$init(str);
                            }
                        } catch (e) {
                            console.log(`${COLORS.red}[!] Failed to dynamically patch SSLPeerUnverifiedException ${e}${COLORS.reset}`);
                        }
                    }

                    if (MODE.HttpsURLConnection) {
                        const colorKey = randomColor();
                        const HttpsURLConnection = loadJava("javax.net.ssl.HttpsURLConnection");
                        try {
                            HttpsURLConnection.setDefaultHostnameVerifier.implementation = function (hostnameVerifier) {
                                console.log(`${colorKey}  --> HttpsURLConnection [DefaultHostnameVerifier]${COLORS.reset}`);
                            };
                            console.log("[+] HttpsURLConnection [DefaultHostnameVerifier]");
                        } catch (e) {
                            console.log("[ ] HttpsURLConnection [DefaultHostnameVerifier]");
                        }

                        try {
                            HttpsURLConnection.setSSLSocketFactory.implementation = function (SSLSocketFactory) {
                                console.log(`${colorKey}  --> HttpsURLConnection [SSLSocketFactory]${COLORS.reset}`);
                            };
                            console.log("[+] HttpsURLConnection [SSLSocketFactory]");
                        } catch (e) {
                            console.log("[ ] HttpsURLConnection [SSLSocketFactory]");
                        }

                        try {
                            HttpsURLConnection.setHostnameVerifier.implementation = function (hostnameVerifier) {
                                console.log(`${colorKey}  --> HttpsURLConnection [HostnameVerifier]${COLORS.reset}`);
                            };
                            console.log("[+] HttpsURLConnection [HostnameVerifier]");
                        } catch (e) {
                            console.log("[ ] HttpsURLConnection [HostnameVerifier]");
                        }

                    }

                    if (MODE.SSLContext) {
                        // TrustManager (Android < 7)
                        const colorKey = randomColor();
                        try {
                            const X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
                            const SSLContext = Java.use("javax.net.ssl.SSLContext");

                            const TrustManager = Java.registerClass({
                                // Implement a custom TrustManager
                                name: "dev.asd.test.TrustManager",
                                implements: [X509TrustManager],
                                methods: {
                                    checkClientTrusted: function (chain, authType) {
                                    },
                                    checkServerTrusted: function (chain, authType) {
                                    },
                                    getAcceptedIssuers: function () {
                                        return [];
                                    }
                                }
                            });

                            // Prepare the TrustManager array to pass to SSLContext.init()
                            const TrustManagers = [TrustManager.$new()];
                            // Get a handle on the init() on the SSLContext class
                            const SSLContext_init = SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom");
                            // Override the init method, specifying the custom TrustManager
                            SSLContext_init.implementation = function (keyManager, trustManager, secureRandom) {
                                console.log(`${colorKey}  --> TrustManager [SSLContext] (Android < 7)${COLORS.reset}`);
                                SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
                            };
                            console.log("[+] TrustManager [SSLContext] (Android < 7)");
                        } catch (e) {
                            console.log("[ ] TrustManager [SSLContext] (Android < 7)");
                        }
                    }

                    if (MODE.TrustManagerImpl) {
                        // TrustManagerImpl (Android > 7)
                        const colorKey = randomColor();
                        const TrustManagerImpl = loadJava("com.android.org.conscrypt.TrustManagerImpl");
                        try {
                            const ArrayList = Java.use("java.util.ArrayList");
                            TrustManagerImpl.checkTrustedRecursive.implementation = function (certs, ocspData, tlsSctData, host, clientAuth, untrustedChain, trustAnchorChain, used) {
                                console.log(`${colorKey}  --> TrustManagerImpl [TrustedRecursive] (Android > 7): ${host}${COLORS.reset}`);
                                return ArrayList.$new();
                            };
                            console.log("[+] TrustManagerImpl [TrustedRecursive] (Android > 7)");
                        } catch (e) {
                            console.log("[ ] TrustManagerImpl [TrustedRecursive] (Android > 7)");
                        }

                        try {
                            TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
                                console.log(`${colorKey}  --> TrustManagerImpl [verifyChain] (Android > 7): ${host}${COLORS.reset}`);
                                return untrustedChain;
                            };
                            console.log("[+] TrustManagerImpl [verifyChain] (Android > 7)");
                        } catch (e) {
                            console.log("[ ] TrustManagerImpl [verifyChain] (Android > 7)");
                        }
                    }

                    if (MODE.OkHTTPv3) {
                        const colorKey = randomColor();
                        const CertificatePinner = loadJava("okhttp3.CertificatePinner");
                        try {
                            CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function (a, b) {
                                console.log(`${colorKey}  --> OkHTTPv3 [List]: ${a}${COLORS.reset}`);
                            };
                            console.log("[+] OkHTTPv3 [List]");
                        } catch (e) {
                            console.log("[ ] OkHTTPv3 [List]");
                        }

                        try {
                            CertificatePinner.check.overload("java.lang.String", "java.security.cert.Certificate").implementation = function (a, b) {
                                console.log(`${colorKey}  --> OkHTTPv3 [Certificate]: ${a}${COLORS.reset}`);
                            };
                            console.log("[+] OkHTTPv3 [Certificate]");
                        } catch (e) {
                            console.log("[ ] OkHTTPv3 [Certificate]");
                        }

                        try {
                            CertificatePinner.check.overload("java.lang.String", "[Ljava.security.cert.Certificate;").implementation = function (a, b) {
                                console.log(`${colorKey}  --> OkHTTPv3 [Array]: ${a}${COLORS.reset}`);
                            };
                            console.log("[+] OkHTTPv3 [Array]");
                        } catch (e) {
                            console.log("[ ] OkHTTPv3 [Array]");
                        }

                        try {
                            CertificatePinner.check$okhttp.overload("java.lang.String", "kotlin.jvm.functions.Function0").implementation = function (a, b) {
                                console.log(`${colorKey}  --> OkHTTPv3 [Function]: ${a}${COLORS.reset}`);
                            };
                            console.log("[+] OkHTTPv3 [Function]");
                        } catch (e) {
                            console.log("[ ] OkHTTPv3 [Function]");
                        }
                    }

                    if (MODE.Trustkit) {
                        const colorKey = randomColor();
                        const OkHostnameVerifier = loadJava("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
                        const PinningTrustManager = loadJava("com.datatheorem.android.trustkit.pinning.PinningTrustManager");
                        try {
                            OkHostnameVerifier.verify.overload("java.lang.String", "javax.net.ssl.SSLSession").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Trustkit OkHostnameVerifier [SSLSession]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Trustkit OkHostnameVerifier [SSLSession]");
                        } catch (e) {
                            console.log("[ ] Trustkit OkHostnameVerifier [SSLSession]");
                        }

                        try {
                            OkHostnameVerifier.verify.overload("java.lang.String", "java.security.cert.X509Certificate").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Trustkit OkHostnameVerifier [X509Certificate]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Trustkit OkHostnameVerifier [X509Certificate]");
                        } catch (e) {
                            console.log("[ ] Trustkit OkHostnameVerifier [X509Certificate]");
                        }

                        try {
                            PinningTrustManager.checkServerTrusted.overload("[Ljava.security.cert.X509Certificate;", "java.lang.String").implementation = function (chain, authType) {
                                console.log(`${colorKey}  --> Trustkit PinningTrustManager${COLORS.reset}`);
                            };
                            console.log("[+] Trustkit PinningTrustManager");
                        } catch (e) {
                            console.log("[ ] Trustkit PinningTrustManager");
                        }
                    }

                    if (MODE.TitaniumPinningTrustManager) {
                        const colorKey = randomColor();
                        const PinningTrustManager = loadJava("appcelerator.https.PinningTrustManager");
                        try {
                            PinningTrustManager.checkServerTrusted.implementation = function (chain, authType) {
                                console.log(`${colorKey}  --> Titanium [PinningTrustManager]${COLORS.reset}`);
                            };
                            console.log("[+] Titanium [PinningTrustManager]");
                        } catch (e) {
                            console.log("[ ] Titanium [PinningTrustManager]");
                        }
                    }

                    if (MODE.FabricPinningTrustManager) {
                        const colorKey = randomColor();
                        const PinningTrustManager = loadJava("io.fabric.sdk.android.services.network.PinningTrustManager");
                        try {
                            PinningTrustManager.checkServerTrusted.implementation = function (chain, authType) {
                                console.log(`${colorKey}  --> Fabric [PinningTrustManager]${COLORS.reset}`);
                            };
                            console.log("[+] Fabric [PinningTrustManager]");
                        } catch (e) {
                            console.log("[ ] Fabric [PinningTrustManager]");
                        }
                    }

                    if (MODE.ConscryptOpenSSLSocketImpl) {
                        const colorKey = randomColor();
                        const OpenSSLSocketImpl = loadJava("com.android.org.conscrypt.OpenSSLSocketImpl");
                        try {
                            OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, JavaObject, authMethod) {
                                console.log(`${colorKey}  --> Conscrypt [OpenSSLSocketImpl] (Refs)${COLORS.reset}`);
                            };
                            console.log("[+] Conscrypt [OpenSSLSocketImpl] (Refs)");
                        } catch (e) {
                            console.log("[ ] Conscrypt [OpenSSLSocketImpl] (Refs)");
                        }

                        try {
                            OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certChain, authMethod) {
                                console.log(`${colorKey}  --> Conscrypt [OpenSSLSocketImpl] (Chain)${COLORS.reset}`);
                            };
                            console.log("[+] Conscrypt [OpenSSLSocketImpl] (Chain)");
                        } catch (e) {
                            console.log("[ ] Conscrypt [OpenSSLSocketImpl] (Chain)");
                        }
                    }

                    if (MODE.ConscryptOpenSSLEngineSocketImpl) {
                        const colorKey = randomColor();
                        const OpenSSLEngineSocketImpl = loadJava("com.android.org.conscrypt.OpenSSLEngineSocketImpl");
                        try {
                            OpenSSLEngineSocketImpl.verifyCertificateChain.overload("[Ljava.lang.Long;", "java.lang.String").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Conscrypt [OpenSSLEngineSocketImpl]: ${b}${COLORS.reset}`);
                            };
                            console.log("[+] Conscrypt [OpenSSLEngineSocketImpl]");
                        } catch (e) {
                            console.log("[ ] Conscrypt [OpenSSLEngineSocketImpl]");
                        }
                    }

                    if (MODE.ApacheOpenSSLSocketImpl) {
                        const colorKey = randomColor();
                        const OpenSSLSocketImpl = loadJava("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
                        try {
                            OpenSSLSocketImpl.verifyCertificateChain.implementation = function (asn1DerEncodedCertificateChain, authMethod) {
                                console.log(`${colorKey}  --> Apache [OpenSSLSocketImpl]${COLORS.reset}`);
                            };
                            console.log("[+] Apache [OpenSSLSocketImpl]");
                        } catch (e) {
                            console.log("[ ] Apache [OpenSSLSocketImpl]");
                        }
                    }

                    if (MODE.PhoneGapsslCertificateChecker) {
                        const colorKey = randomColor();
                        const sslCertificateChecker = loadJava("nl.xservices.plugins.sslCertificateChecker");
                        try {
                            sslCertificateChecker.execute.overload("java.lang.String", "org.json.JSONArray", "org.apache.cordova.CallbackContext").implementation = function (a, b, c) {
                                console.log(`${colorKey}  --> PhoneGap [sslCertificateChecker]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] PhoneGap [sslCertificateChecker]");
                        } catch (e) {
                            console.log("[ ] PhoneGap [sslCertificateChecker]");
                        }
                    }

                    if (MODE.IBMMobileFirst) {
                        const colorKey = randomColor();
                        const MobileFirst = loadJava("com.worklight.wlclient.api.WLClient");
                        try {
                            MobileFirst.getInstance().pinTrustedCertificatePublicKey.overload("java.lang.String").implementation = function (cert) {
                                console.log(`${colorKey}  --> IBM [MobileFirst] (String): ${cert}${COLORS.reset}`);
                            };
                            console.log("[+] IBM [MobileFirst] (String)");
                        } catch (e) {
                            console.log("[ ] IBM [MobileFirst] (String)");
                        }

                        try {
                            MobileFirst.getInstance().pinTrustedCertificatePublicKey.overload("[Ljava.lang.String;").implementation = function (cert) {
                                console.log(`${colorKey}  --> IBM [MobileFirst] (Array): ${cert}${COLORS.reset}`);
                            };
                            console.log("[+] IBM [MobileFirst] (Array)");
                        } catch (e) {
                            console.log("[ ] IBM [MobileFirst] (Array)");
                        }
                    }

                    if (MODE.IBMWorkLight) {
                        const colorKey = randomColor();
                        const WorkLight = loadJava("com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning");
                        try {
                            WorkLight.verify.overload("java.lang.String", "javax.net.ssl.SSLSocket").implementation = function (a, b) {
                                console.log(`${colorKey}  --> IBM [WorkLight] (SSLSocket): ${a}${COLORS.reset}`);
                            };
                            console.log("[+] IBM [WorkLight] (SSLSocket)");
                        } catch (e) {
                            console.log("[ ] IBM [WorkLight] (SSLSocket)");
                        }

                        try {
                            WorkLight.verify.overload("java.lang.String", "java.security.cert.X509Certificate").implementation = function (a, b) {
                                console.log(`${colorKey}  --> IBM [WorkLight] (X509Certificate): ${a}${COLORS.reset}`);
                            };
                            console.log("[+] IBM [WorkLight] (X509Certificate)");
                        } catch (e) {
                            console.log("[ ] IBM [WorkLight] (X509Certificate)");
                        }

                        try {
                            WorkLight.verify.overload("java.lang.String", "[Ljava.lang.String;", "[Ljava.lang.String;").implementation = function (a, b) {
                                console.log(`${colorKey}  --> IBM [WorkLight] (String): ${a}${COLORS.reset}`);
                            };
                            console.log("[+] IBM [WorkLight] (String)");
                        } catch (e) {
                            console.log("[ ] IBM [WorkLight] (String)");
                        }

                        try {
                            WorkLight.verify.overload("java.lang.String", "javax.net.ssl.SSLSession").implementation = function (a, b) {
                                console.log(`${colorKey}  --> IBM [WorkLight] (SSLSession): ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] IBM [WorkLight] (SSLSession)");
                        } catch (e) {
                            console.log("[ ] IBM [WorkLight] (SSLSession)");
                        }
                    }

                    if (MODE.ConscryptCertPinManager) {
                        const colorKey = randomColor();
                        const CertPinManager = loadJava("com.android.org.conscrypt.CertPinManager");
                        try {
                            CertPinManager.checkChainPinning.overload("java.lang.String", "java.util.List").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Conscrypt [CertPinManager] (List): ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Conscrypt [CertPinManager] (List)");
                        } catch (e) {
                            console.log("[ ] Conscrypt [CertPinManager] (List)");
                        }

                        try {
                            CertPinManager.isChainValid.overload("java.lang.String", "java.util.List").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Conscrypt [CertPinManager] (Legacy): ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Conscrypt [CertPinManager] (Legacy)");
                        } catch (e) {
                            console.log("[ ] Conscrypt [CertPinManager] (Legacy)");
                        }
                    }

                    if (MODE.NetsecurityCertPinManager) {
                        const colorKey = randomColor();
                        const CertPinManager = loadJava("com.commonsware.cwac.netsecurity.conscrypt.CertPinManager");
                        try {
                            CertPinManager.isChainValid.overload("java.lang.String", "java.util.List").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Netsecurity [CertPinManager]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Netsecurity [CertPinManager]");
                        } catch (e) {
                            console.log("[ ] Netsecurity [CertPinManager]");
                        }
                    }

                    if (MODE.AndroidgapWorkLight) {
                        const colorKey = randomColor();
                        const Worklight = loadJava("com.worklight.androidgap.plugin.WLCertificatePinningPlugin");
                        try {
                            Worklight.execute.overload("java.lang.String", "org.json.JSONArray", "org.apache.cordova.CallbackContext").implementation = function (a, b, c) {
                                console.log(`${colorKey}  --> Android [WorkLight]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Android [WorkLight]");
                        } catch (e) {
                            console.log("[ ] Android [WorkLight]");
                        }
                    }

                    if (MODE.NettyFingerprintTrustManagerFactory) {
                        const colorKey = randomColor();
                        const FingerprintTrustManagerFactory = loadJava("io.netty.handler.ssl.util.FingerprintTrustManagerFactory");
                        try {
                            FingerprintTrustManagerFactory.checkTrusted.implementation = function (type, chain) {
                                console.log(`${colorKey}  --> Netty [FingerprintTrustManagerFactory]${COLORS.reset}`);
                            };
                            console.log("[+] Netty [FingerprintTrustManagerFactory]");
                        } catch (e) {
                            console.log("[ ] Netty [FingerprintTrustManagerFactory]");
                        }
                    }

                    if (MODE.SquareupCertificatePinner) {
                        // OkHTTP < v3
                        const colorKey = randomColor();
                        const CertificatePinner = loadJava("com.squareup.okhttp.CertificatePinner");
                        try {
                            CertificatePinner.check.overload("java.lang.String", "java.security.cert.Certificate").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Squareup [CertificatePinner] (Certificate): ${a}${COLORS.reset}`);
                            };
                            console.log("[+] Squareup [CertificatePinner] (Certificate)");
                        } catch (e) {
                            console.log("[ ] Squareup [CertificatePinner] (Certificate)");
                        }

                        try {
                            CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Squareup [CertificatePinner] (List): ${a}${COLORS.reset}`);
                            };
                            console.log("[+] Squareup [CertificatePinner] (List)");
                        } catch (e) {
                            console.log("[ ] Squareup [CertificatePinner] (List)");
                        }
                    }

                    if (MODE.SquareupOkHostnameVerifier) {
                        // OkHTTP v3
                        const colorKey = randomColor();
                        const OkHostnameVerifier = loadJava("com.squareup.okhttp.internal.tls.OkHostnameVerifier");
                        try {
                            OkHostnameVerifier.verify.overload("java.lang.String", "java.security.cert.X509Certificate").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Squareup [OkHostnameVerifier] (X509Certificate): ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Squareup [OkHostnameVerifier] (X509Certificate)");
                        } catch (e) {
                            console.log("[ ] Squareup [OkHostnameVerifier] (X509Certificate)");
                        }

                        try {
                            OkHostnameVerifier.verify.overload("java.lang.String", "javax.net.ssl.SSLSession").implementation = function (a, b) {
                                console.log(`${colorKey}  --> Squareup [OkHostnameVerifier] (SSLSession): ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Squareup [OkHostnameVerifier] (SSLSession)");
                        } catch (e) {
                            console.log("[ ] Squareup [OkHostnameVerifier] (SSLSession)");
                        }
                    }

                    if (MODE.AndroidWebViewClient) {
                        const colorKey = randomColor();
                        const WebViewClient = loadJava("android.webkit.WebViewClient");

                        try {
                            WebViewClient.onReceivedSslError.overload("android.webkit.WebView", "android.webkit.SslErrorHandler", "android.net.http.SslError").implementation = function (obj1, obj2, obj3) {
                                console.log(`${colorKey}  --> Android [WebViewClient] (SslErrorHandler)${COLORS.reset}`);
                            };
                            console.log("[+] Android [WebViewClient] (SslErrorHandler)");
                        } catch (e) {
                            console.log("[ ] Android [WebViewClient] (SslErrorHandler)");
                        }

                        try {
                            WebViewClient.onReceivedSslError.overload("android.webkit.WebView", "android.webkit.WebResourceRequest", "android.webkit.WebResourceError").implementation = function (obj1, obj2, obj3) {
                                console.log(`${colorKey}  --> Android [WebViewClient] (SSLWebResourceError)${COLORS.reset}`);
                            };
                            console.log("[+] Android [WebViewClient] (SSLWebResourceError)");
                        } catch (e) {
                            console.log("[ ] Android [WebViewClient] (SSLWebResourceError)");
                        }

                        try {
                            WebViewClient.onReceivedError.overload("android.webkit.WebView", "int", "java.lang.String", "java.lang.String").implementation = function (obj1, obj2, obj3, obj4) {
                                console.log(`${colorKey}  --> Android [WebViewClient] (String)${COLORS.reset}`);
                            };
                            console.log("[+] Android [WebViewClient] (String)");
                        } catch (e) {
                            console.log("[ ] Android [WebViewClient] (String)");
                        }

                        try {
                            WebViewClient.onReceivedError.overload("android.webkit.WebView", "android.webkit.WebResourceRequest", "android.webkit.WebResourceError").implementation = function (obj1, obj2, obj3) {
                                console.log(`${colorKey}  --> Android [WebViewClient] (WebResourceError)${COLORS.reset}`);
                            };
                            console.log("[+] Android [WebViewClient] (WebResourceError)");
                        } catch (e) {
                            console.log("[ ] Android [WebViewClient] (WebResourceError)");
                        }
                    }

                    if (MODE.ApacheWebViewClient) {
                        const colorKey = randomColor();
                        const CordovaWebViewClient = loadJava("org.apache.cordova.CordovaWebViewClient");
                        try {
                            CordovaWebViewClient.onReceivedSslError.overload("android.webkit.WebView", "android.webkit.SslErrorHandler", "android.net.http.SslError").implementation = function (obj1, obj2, obj3) {
                                console.log(`${colorKey}  --> Apache [WebViewClient]${COLORS.reset}`);
                                obj3.proceed();
                            };
                            console.log("[+] Apache [WebViewClient]");
                        } catch (e) {
                            console.log("[ ] Apache [WebViewClient]");
                        }
                    }

                    if (MODE.BoyeAbstractVerifier) {
                        const colorKey = randomColor();
                        const AbstractVerifier = loadJava("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
                        try {
                            AbstractVerifier.verify.implementation = function (host, ssl) {
                                console.log(`${colorKey}  --> Boye [AbstractVerifier]: ${host}${COLORS.reset}`);
                            };
                            console.log("[+] Boye [AbstractVerifier]");
                        } catch (e) {
                            console.log("[ ] Boye [AbstractVerifier]");
                        }
                    }

                    if (MODE.ApacheAbstractVerifier) {
                        const colorKey = randomColor();
                        const AbstractVerifier = loadJava("org.apache.http.conn.ssl.AbstractVerifier");
                        try {
                            AbstractVerifier.verify.implementation = function (a, b, c, d) {
                                console.log(`${colorKey}  --> Apache [AbstractVerifier]: ${a}${COLORS.reset}`);
                            };
                            console.log("[+] Apache [AbstractVerifier]");
                        } catch (e) {
                            console.log("[ ] Apache [AbstractVerifier]");
                        }
                    }

                    if (MODE.Appmattus) {
                        const colorKey = randomColor();
                        const Transparency = loadJava("com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor");
                        try {
                            Transparency.intercept.implementation = function (a) {
                                console.log(`${colorKey}  --> Appmattus [Transparency]${COLORS.reset}`);
                                return a.proceed(a.request());
                            };
                            console.log("[+] Appmattus [Transparency]");
                        } catch (e) {
                            console.log("[ ] Appmattus [Transparency]");
                        }
                    }

                    if (MODE.ChromiumCronet) {
                        const colorKey = randomColor();
                        const CronetEngineBuilderImpl = loadJava("org.chromium.net.impl.CronetEngineBuilderImpl");
                        try {
                            CronetEngineBuilderImpl.enablePublicKeyPinningBypassForLocalTrustAnchors.overload("boolean").implementation = function (a) {
                                console.log(`${colorKey}  --> Chromium [CronetEngineBuilderImpl] (LocalTrustAnchors)${COLORS.reset}`);
                                return CronetEngineBuilderImpl.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
                            };
                            console.log("[+] Chromium [CronetEngineBuilderImpl] (LocalTrustAnchors)");
                        } catch (e) {
                            console.log("[ ] Chromium [CronetEngineBuilderImpl] (LocalTrustAnchors)");
                        }

                        try {
                            CronetEngineBuilderImpl.addPublicKeyPins.overload("java.lang.String", "java.util.Set", "boolean", "java.util.Date").implementation = function (hostName, pinsSha256, includeSubdomains, expirationDate) {
                                console.log(`${colorKey}  --> Chromium [CronetEngineBuilderImpl] (PublicKey): ${hostName}${COLORS.reset}`);
                                return CronetEngineBuilderImpl.addPublicKeyPins.call(this, hostName, pinsSha256, includeSubdomains, expirationDate);
                            };
                            console.log("[+] Chromium [CronetEngineBuilderImpl] (PublicKey)");
                        } catch (e) {
                            console.log("[ ] Chromium [CronetEngineBuilderImpl] (PublicKey)");
                        }
                    }

                    if (MODE.Flutter) {
                        const colorKey = randomColor();
                        const HttpCertificatePinning = loadJava("diefferson.http_certificate_pinning.HttpCertificatePinning");
                        const SslPinningPlugin = loadJava("com.macif.plugin.sslpinningplugin.SslPinningPlugin");
                        try {
                            HttpCertificatePinning.checkConnexion.overload("java.lang.String", "java.util.List", "java.util.Map", "int", "java.lang.String").implementation = function (a, b, c, d, e) {
                                console.log(`${colorKey}  --> Flutter [HttpCertificatePinning]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Flutter [HttpCertificatePinning]");
                        } catch (e) {
                            console.log("[ ] Flutter [HttpCertificatePinning]");
                        }

                        try {
                            SslPinningPlugin.checkConnexion.overload("java.lang.String", "java.util.List", "java.util.Map", "int", "java.lang.String").implementation = function (a, b, c, d, e) {
                                console.log(`${colorKey}  --> Flutter [SslPinningPlugin]: ${a}${COLORS.reset}`);
                                return true;
                            };
                            console.log("[+] Flutter [SslPinningPlugin]");
                        } catch (e) {
                            console.log("[ ] Flutter [SslPinningPlugin]");
                        }
                    }

                }
            );
        } else {
            console.log(`${COLORS.red}[!] Java unavailable${COLORS.reset}`);
        }

        console.log("Capturing setup completed");
        console.log("---");
    }, 0);
}

Flutter

Flutter App의 Burp 잡는 법

/* Global variables */
var appId = null;
var appId_iOS = null;

var BURP_PROXY_IP = null;
var BURP_PROXY_PORT = null;

var flutter_base = null;
var flutter_size = null;

var PT_LOAD_rodata_p_memsz = null;
var PT_LOAD_text_p_vaddr = null;
var PT_LOAD_text_p_memsz = null;
var PT_GNU_RELRO_p_vaddr = null;
var PT_GNU_RELRO_p_memsz = null;

var TEXT_segment_text_section_offset = null;
var TEXT_segment_text_section_size = null;
var TEXT_segment_cstring_section_offset = null;
var TEXT_segment_cstring_section_size = null;
var DATA_segment_const_section_offset = null;
var DATA_segment_const_section_size = null;

var ssl_client_string_pattern_found_addr = null;
var verify_cert_chain_func_addr = null;
var handshake_string_pattern_found_addr = null;
var verify_peer_cert_func_addr = null;

var Socket_CreateConnect_string_pattern_found_addr = null;
var Socket_CreateConnect_func_addr = null;

var GetSockAddr_func_addr = null;
var sockaddr = null;
/* Global variables */

/* Util functions */
// Find application package name
function findAppId() {
    if (Process.platform === "linux") {
        var pm = Java.use('android.app.ActivityThread').currentApplication();
        return pm.getApplicationContext().getPackageName();
    } else {
        return ObjC.classes.NSBundle.mainBundle().bundleIdentifier().toString();
    }
}

// Convert hex to byte string
function convertHexToByteString(hexString) {
    // Remove the '0x' prefix
    let cleanHexString = hexString.startsWith('0x') ? hexString.slice(2) : hexString;

    // Pad with a leading zero if the length is odd
    if (cleanHexString.length % 2 !== 0) {
        cleanHexString = '0' + cleanHexString;
    }

    // Split the string into pairs of two characters
    let byteArray = cleanHexString.match(/.{1,2}/g);

    // Reverse the order of the byte pairs
    byteArray.reverse();

    // Join the byte pairs with spaces
    let byteString = byteArray.join(' ');

    return byteString;
}

// Convert ip string (e.g, "192.168.0.12") to byte array
function convertIpToByteArray(ipString) {
    // Split the IP address into its components
    let octets = ipString.split('.');

    // Convert each octet to a hexadecimal number and then to a byte
    let byteArray = octets.map(octet => parseInt(octet, 10));

    return byteArray;
}

// Convert ArrayBuffer to hex string
function convertArrayBufferToHex(buffer) {
    let hexArray = [];
    let uint8Array = new Uint8Array(buffer);
    for (let byte of uint8Array) {
        hexArray.push(byte.toString(16).padStart(2, '0'));
    }
    return hexArray.join(' ');
}

// Byte flip
function byteFlip(number) {
    // Extract the high and low bytes
    let highByte = (number >> 8) & 0xFF;
    let lowByte = number & 0xFF;

    // Swap the high and low bytes
    let flippedNumber = (lowByte << 8) | highByte;

    return flippedNumber;
}

// Memory scan
function scanMemory(scan_start_addr, scan_size, pattern, for_what) {
    Memory.scan(scan_start_addr, scan_size, pattern, {
        onMatch: function(address, size){
            if (for_what == "ssl_client") {
                ssl_client_string_pattern_found_addr = address;
                console.log(`[*] ssl_client string pattern found at: ${address}`);
            } 
            else if (for_what == "ssl_client_adrp_add") {
                var adrp, add;
                var disasm = Instruction.parse(address);
                if (disasm.mnemonic == "adrp") {
                    adrp = disasm.operands.find(op => op.type === 'imm')?.value;
                    
                    disasm = Instruction.parse(disasm.next);
                    if (disasm.mnemonic != "add") {
                        disasm = Instruction.parse(disasm.next);
                    }
                    add = disasm.operands.find(op => op.type === 'imm')?.value;

                    if (adrp != undefined && add != undefined && ptr(adrp).add(add).toString() == ssl_client_string_pattern_found_addr.toString()) {
                        console.log(`[*] Found adrp add address: ${address}`);
                        // As we trace back, disassemble to find the address of the verify_cert_chain function (https://blog.weghos.com/flutter/engine/third_party/boringssl/src/ssl/ssl_x509.cc.html#_ZN4bsslL41ssl_crypto_x509_session_verify_cert_chainEP14ssl_session_stPNS_13SSL_HANDSHAKEEPh)
                        for (let off = 0;; off += 4) {
                            disasm = Instruction.parse(address.sub(off));
                            if (disasm.mnemonic == "sub") {
                                disasm = Instruction.parse(disasm.next);
                                if (disasm.mnemonic == "stp" || disasm.mnemonic == "str") {
                                    verify_cert_chain_func_addr = address.sub(off);
                                    console.log(`[*] Found verify_cert_chain function address: ${verify_cert_chain_func_addr}`);
                                    break;
                                }
                            } else {
                                continue;
                            }
                        }
                    }
                }
            }
            else if (for_what == "ssl_client_lea_rdi_rip") {
                /* opcode
                    lea rdi, [rip - 0xabcd]
                */
                var rdi, rip, disp;
                var disasm = Instruction.parse(address);
                if (disasm.mnemonic == "lea") {
                    rip = disasm.next;
                    disp = disasm.operands.find(op => op.type === 'mem')?.value.disp;
                    rdi = rip.add(disp);

                    if (rip != undefined && rdi != undefined && ptr(rdi).toString() == ssl_client_string_pattern_found_addr.toString()) {
                        console.log(`[*] Found lea rdi rip address: ${address}`);
                        // As we trace back, disassemble to find the address of the verify_cert_chain function (https://blog.weghos.com/flutter/engine/third_party/boringssl/src/ssl/ssl_x509.cc.html#_ZN4bsslL41ssl_crypto_x509_session_verify_cert_chainEP14ssl_session_stPNS_13SSL_HANDSHAKEEPh)
                        for (let off = 0;; off += 1) {
                            try {
                                disasm = Instruction.parse(address.sub(off));
                                if (disasm.mnemonic == "push" && disasm.opStr == "rbp") {
                                    if (Instruction.parse(disasm.next) != 'push r15') {
                                        continue;
                                    }
                                    verify_cert_chain_func_addr = address.sub(off);
                                    console.log(`[*] Found verify_cert_chain function address: ${verify_cert_chain_func_addr}`);
                                    break; 
                                } else {
                                    continue;
                                } 
                            } catch (error) {
                                continue;
                            }
                        }
                    }
                }
            }
            else if (for_what == "handshake") {
                for (let off = 0;; off += 1) {
                    var arrayBuff = new Uint8Array(ptr(address).sub(0x6).sub(off).readByteArray(6));
                    var hexarray = convertArrayBufferToHex(arrayBuff);
                    if (hexarray == "2e 2e 2f 2e 2e 2f") {  // "../../"
                        handshake_string_pattern_found_addr = ptr(address).sub(0x6).sub(off);
                        console.log(`[*] handshake string pattern found at: ${address}`);
                        break;
                    }
                    else {
                        continue;
                    }
                }
                // Get the iOS app id. if it's too early app crashes when spawning the iOS flutter app. this location is safe.
                appId_iOS = findAppId();
            }
            else if (for_what == "handshake_adrp_add") {
                var adrp, add;
                var disasm = Instruction.parse(address);
                if (disasm.mnemonic == "adrp") {
                    adrp = disasm.operands.find(op => op.type === 'imm')?.value;
                    
                    disasm = Instruction.parse(disasm.next);
                    if (disasm.mnemonic != "add") {
                        disasm = Instruction.parse(disasm.next);
                    }
                    add = disasm.operands.find(op => op.type === 'imm')?.value;

                    if (adrp != undefined && add != undefined && ptr(adrp).add(add).toString() == handshake_string_pattern_found_addr.toString()) {
                        console.log(`[*] Found adrp add address: ${address}`);
                        // As we trace back, disassemble to find the address of the ssl_verify_peer_cert function (https://blog.weghos.com/flutter/engine/third_party/boringssl/src/ssl/handshake.cc.html#_ZN4bssl20ssl_verify_peer_certEPNS_13SSL_HANDSHAKEE)
                        for (let off = 0;; off += 4) {
                            disasm = Instruction.parse(address.sub(off));
                            if (disasm.mnemonic == "sub") {
                                disasm = Instruction.parse(disasm.next);
                                if (disasm.mnemonic == "stp" || disasm.mnemonic == "str") {
                                    verify_peer_cert_func_addr = address.sub(off);
                                    console.log(`[*] Found verify_peer_cert function address: ${verify_peer_cert_func_addr}`);
                                    break;
                                }
                            } else {
                                continue;
                            }
                        }
                    }
                }
            }
            else if (for_what == "Socket_CreateConnect") {
                Socket_CreateConnect_string_pattern_found_addr = address;
                console.log(`[*] Socket_CreateConnect string pattern found at: ${address}`);
            }
            else if (for_what == "Socket_CreateConnect_func_addr") {
                Socket_CreateConnect_func_addr = address.sub(0x10).readPointer();
                console.log(`[*] Found Socket_CreateConnect function address: ${Socket_CreateConnect_func_addr}`);
                /* arm64
                    Socket_CreateConnect function looks like this.
                    SUB             SP, SP, #0xD0
                    STR             X30, [SP,#0xD0+var_30]
                    STP             X22, X21, [SP,#0xD0+var_20]
                    STP             X20, X19, [SP,#0xD0+var_10]
                    MOV             W1, #1
                    MOV             X19, X0
                    BL              sub_89E20C
                    ADD             X1, SP, #0xD0+var_B0
                    BL              loc_67C15C   <---------------- branch to GetSockAddr function
                    MOV             W1, #2
                    MOV             X0, X19
                    BL              sub_89E20C
                */

                /* x64
                    push            rbp
                    push            r15
                    push            r14
                    push            r13
                    push            r12
                    push            rbx
                    sub             rsp, 498h
                    mov             rbx, rdi
                    mov             esi, 1
                    call            sub_AB2790
                    lea             rsi, [rsp+4C8h+addr]
                    mov             rdi, rax
                    call            sub_8EEB50  <---------------- branch to GetSockAddr function
                    mov             rdi, rbx
                    mov             esi, 2
                    call            sub_AB2790
                */
               
                if (Process.arch == 'arm64') {
                    var bl_count = 0;
                    for (let off = 0;; off += 4) {
                        let disasm = Instruction.parse(Socket_CreateConnect_func_addr.add(off));
                        if (disasm.mnemonic == "bl") {
                            bl_count++;
                            if (bl_count == 2) {
                                GetSockAddr_func_addr = ptr(disasm.operands.find(op => op.type === 'imm')?.value);
                                console.log(`[*] Found GetSockAddr function address: ${GetSockAddr_func_addr}`);
                                break;
                            } else {
                                continue;
                            }
                        }
                    } 
                } else if (Process.arch == 'x64') {
                    var call_count = 0;
                    for (let off = 0;; off += 1) 
                    {
                        try {
                            let disasm = Instruction.parse(Socket_CreateConnect_func_addr.add(off));
                            if (disasm.mnemonic == "call") {
                                call_count++;
                                if (call_count == 2) {
                                    GetSockAddr_func_addr = ptr(disasm.operands.find(op => op.type === 'imm')?.value);
                                    console.log(`[*] Found GetSockAddr function address: ${GetSockAddr_func_addr}`);
                                    break;
                                } else {
                                    continue;
                                }
                            }
                        } catch (error) {
                            continue;
                        }
                    }
                }               
            }
        }, 
        onComplete: function(){
            // Scan adrp add opcode on the .text section to find the function which has "ssl_client" string
            if (for_what == "ssl_client" && ssl_client_string_pattern_found_addr != null) {
                if (Process.arch == 'arm64') {
                    var adrp_add_pattern = "?9 ?? ?? ?0 29 ?? ?? 91";
                    if (appId == "com.alibaba.intl.android.apps.poseidon") {
                        // alibaba.com (android) adrp add pattern is different
                        adrp_add_pattern = "?9 ?? ?? ?0 ?? ?? ?? ?? 29 ?? ?? 91";
                    }
                    scanMemory(flutter_base.add(PT_LOAD_text_p_vaddr), PT_LOAD_text_p_memsz, adrp_add_pattern, "ssl_client_adrp_add");
                } else if (Process.arch == 'x64') {
                    var lea_rdi_rip_pattern = "48 8d 3d ?? ?? ?? FF";
                    scanMemory(flutter_base.add(PT_LOAD_text_p_vaddr), PT_LOAD_text_p_memsz, lea_rdi_rip_pattern, "ssl_client_lea_rdi_rip");
                }
            }
            else if (for_what == "handshake" && handshake_string_pattern_found_addr != null) {
                var adrp_add_pattern = "?2 ?? 00 ?0 42 ?? ?? 91 00 02 80 52 21 22 80 52 c3 29 80 52";
                // In case we don't get the iOS app id yet, try to get it again after 0.1 second. This happens when spawning the iOS Flutter app
                if (appId_iOS == null) {
                    Thread.sleep(0.1);
                    appId_iOS = findAppId();
                }
                if (appId_iOS == "com.alibaba.sourcing") {
                    // alibaba.com (iOS) adrp add pattern is different
                    adrp_add_pattern = "?3 ?? 00 ?0 63 ?? ?? 91 00 02 80 52 01 00 80 52 22 22 80 52 84 25 80 52"
                }
                scanMemory(flutter_base.add(TEXT_segment_text_section_offset), TEXT_segment_text_section_size, adrp_add_pattern, "handshake_adrp_add");
            }
            // Scan "Socket_CreateConnect" string pattern found address on the .data.rel.ro section to find the address of "Socket_CreateConnect" function
            else if (for_what == "Socket_CreateConnect" && Socket_CreateConnect_string_pattern_found_addr != null) {
                var addr_to_find = convertHexToByteString(Socket_CreateConnect_string_pattern_found_addr.toString());
                if (Process.platform === 'linux') {
                    scanMemory(flutter_base.add(PT_GNU_RELRO_p_vaddr), PT_GNU_RELRO_p_memsz, addr_to_find, "Socket_CreateConnect_func_addr");
                }
                else if (Process.platform === 'darwin') {
                    scanMemory(flutter_base.add(DATA_segment_const_section_offset), DATA_segment_const_section_size, addr_to_find, "Socket_CreateConnect_func_addr");
                }
            }
            console.log("[*] scan memory done");
        }
    })
}
/* Util functions */

/* Some variables and functions for elf parsing */
var O_RDONLY = 0;
var O_WRONLY = 1;
var O_RDWR = 2;
var O_APPEND = 1024;
var O_LARGEFILE = 32768;
var O_CREAT = 64;
var SEEK_SET = 0;
var SEEK_CUR = 1;
var SEEK_END = 2;

var p_types = {
    "PT_NULL":		0,		/* Program header table entry unused */
    "PT_LOAD":		1,		/* Loadable program segment */
    "PT_DYNAMIC":	2,		/* Dynamic linking information */
    "PT_INTERP":	3,		/* Program interpreter */
    "PT_NOTE":		4,		/* Auxiliary information */
    "PT_SHLIB":	    5,		/* Reserved */
    "PT_PHDR":		6,		/* Entry for header table itself */
    "PT_TLS":		7,		/* Thread-local storage segment */
    "PT_NUM":		8,		/* Number of defined types */
    "PT_LOOS":		0x60000000,	/* Start of OS-specific */
    "PT_GNU_EH_FRAME":	0x6474e550,	/* GCC .eh_frame_hdr segment */
    "PT_GNU_STACK":	0x6474e551,	/* Indicates stack executability */
    "PT_GNU_RELRO":	0x6474e552,	/* Read-only after relocation */
    "PT_GNU_PROPERTY":	0x6474e553,	/* GNU property */
    "PT_LOSUNW":	0x6ffffffa,
    "PT_SUNWBSS":	0x6ffffffa,	/* Sun Specific segment */
    "PT_SUNWSTACK":	0x6ffffffb,	/* Stack segment */
    "PT_HISUNW":	0x6fffffff,
    "PT_HIOS":		0x6fffffff,	/* End of OS-specific */
    "PT_LOPROC":	0x70000000,	/* Start of processor-specific */
    "PT_HIPROC":	0x7fffffff,	/* End of processor-specific */
}

function getExportFunction(name, ret, args) {
    var funcPtr;
    funcPtr = Module.getGlobalExportByName(name);
    if (funcPtr === null) {
        console.log("cannot find " + name);
        return null;
    } else {
        var func = new NativeFunction(funcPtr, ret, args);
        if (typeof func === "undefined") {
            console.log("parse error " + name);
            return null;
        }
        return func;
    }
}

var open = getExportFunction("open", "int", ["pointer", "int", "int"])
var close = getExportFunction("close", "int", ["int"]);
var lseek = getExportFunction("lseek", "int", ["int", "int", "int"]);
var read = getExportFunction("read", "int", ["int", "pointer", "int"]);
/* Some variables and functions for elf parsing */

/* Parsing elf function */
function parseElf(base) {
    base = ptr(base);
    var module = Process.findModuleByAddress(base);
    var fd = null;
    if (module !== null) {
        fd = open(Memory.allocUtf8String(module.path), O_RDONLY, 0);
    }
    
    // Read elf header
    var magic = "464c457f"
    var elf_magic = base.readU32()
    if (parseInt(elf_magic).toString(16) != magic) {
        console.log("[!] Wrong magic...ignore")
    }

    var arch = Process.arch
    var is32bit = arch == "arm" ? 1 : 0 // 1:32 0:64

    var size_of_Elf32_Ehdr = 0x34;
    var off_of_Elf32_Ehdr_shoff = 32;
    var off_of_Elf32_Ehdr_phentsize = 42;
    var off_of_Elf32_Ehdr_phnum = 44;
    var off_of_Elf32_Ehdr_shentsize = 46;
    var off_of_Elf32_Ehdr_shnum = 48;
    var off_of_Elf32_Ehdr_shstrndx = 50;

    var size_of_Elf64_Ehdr = 0x40;
    var off_of_Elf64_Ehdr_shoff = 40;
    var off_of_Elf64_Ehdr_phentsize = 54;
    var off_of_Elf64_Ehdr_phnum = 56;
    var off_of_Elf64_Ehdr_shentsize = 58;
    var off_of_Elf64_Ehdr_shnum = 60;
    var off_of_Elf64_Ehdr_shstrndx = 62;

    // Parse Ehdr(Elf header)
    var ehdrs_from_file = null;
    var phoff = is32bit ? size_of_Elf32_Ehdr : size_of_Elf64_Ehdr   // Program header table file offset
    var shoff = is32bit ? base.add(off_of_Elf32_Ehdr_shoff).readU32() : base.add(off_of_Elf64_Ehdr_shoff).readU64();   // Section header table file offset
    if (shoff == 0 && fd != null && fd !== -1) {
        console.log("[!] shoff is 0. Try to get it from the file")
        ehdrs_from_file = Memory.alloc(64);
        lseek(fd, 0, SEEK_SET);
        read(fd, ehdrs_from_file, 64);
        shoff = is32bit ? ehdrs_from_file.add(off_of_Elf32_Ehdr_shoff).readU32() : ehdrs_from_file.add(off_of_Elf64_Ehdr_shoff).readU64();
        console.log(`[*] shoff from the file: ${shoff}`)
    }
    var phentsize = is32bit ? base.add(off_of_Elf32_Ehdr_phentsize).readU16() : base.add(off_of_Elf64_Ehdr_phentsize).readU16();    // Size of entries in the program header table
    if (is32bit && phentsize != 32) {  // 0x20
        console.log("[!] Wrong e_phentsize. Should be 32. Let's assume it's 32");
        phentsize = 32;
    } else if (!is32bit && phentsize != 56) {
        console.log("[!] Wrong e_phentsize. Should be 56. Let's assume it's 56");
        phentsize = 56;
    }
    var phnum = is32bit ? base.add(off_of_Elf32_Ehdr_phnum).readU16() : base.add(off_of_Elf64_Ehdr_phnum).readU16();    // Number of entries in program header table
    // If phnum is 0, try to get it from the file
    if (phnum == 0) {
        if (fd != null && fd !== -1){
            console.log("[!] phnum is 0. Try to get it from the file")
            ehdrs_from_file = Memory.alloc(64);
            lseek(fd, 0, SEEK_SET);
            read(fd, ehdrs_from_file, 64);
            phnum = is32bit ? ehdrs_from_file.add(off_of_Elf32_Ehdr_phnum).readU16() : ehdrs_from_file.add(off_of_Elf64_Ehdr_phnum).readU16();
            if (phnum == 0) {
                console.log("[!] phnum is still 0. Let's assume it's 10. because we just need to find .dynamic section");
                phnum = 10;
            } else {
                console.log(`[*] phnum from the file: ${phnum}`)
            }
        } else {
            console.log("[!] phnum is 0. Let's assume it's 10. because we just need to find .dynamic section")
            phnum = 10;
        }
    }

    var shentsize = is32bit ? base.add(off_of_Elf32_Ehdr_shentsize).readU16() : base.add(off_of_Elf64_Ehdr_shentsize).readU16();    // Size of the section header
    if (is32bit && shentsize != 40) {  // 0x28
        console.log("[!] Wrong e_shentsize. Let's assume it's 40");
        shentsize = 40;
    } else if (!is32bit && shentsize != 64) {
        console.log("[!] Wrong e_shentsize. Let's assume it's 64");
        shentsize = 64;
    }
    var shnum = is32bit ? base.add(off_of_Elf32_Ehdr_shnum).readU16() : base.add(off_of_Elf64_Ehdr_shnum).readU16();    // Number of entries in section header table
    var shstrndx = is32bit ? base.add(off_of_Elf32_Ehdr_shstrndx).readU16() : base.add(off_of_Elf64_Ehdr_shstrndx).readU16();  // Section header table index of the entry associated with the section name string table
    if (shnum == 0 && fd != null && fd !== -1) {
        console.log("[!] shnum is 0. Try to get it from the file");
        ehdrs_from_file = Memory.alloc(64);
        lseek(fd, 0, SEEK_SET);
        read(fd, ehdrs_from_file, 64);
        shnum = is32bit ? ehdrs_from_file.add(off_of_Elf32_Ehdr_shnum).readU16() : ehdrs_from_file.add(off_of_Elf64_Ehdr_shnum).readU16();
        shstrndx = is32bit ? ehdrs_from_file.add(off_of_Elf32_Ehdr_shstrndx).readU16() : ehdrs_from_file.add(off_of_Elf64_Ehdr_shstrndx).readU16();
        console.log(`[*] shnum from the file: ${shnum}, shstrndx from the file: ${shstrndx}`)
    }
    // console.log(`phoff: ${phoff}, shoff: ${shoff}, phentsize: ${phentsize}, phnum: ${phnum}, shentsize: ${shentsize}, shnum: ${shnum}, shstrndx: ${shstrndx}`)

    // Parse Phdr(Program header)
    var phdrs = base.add(phoff)
    for (var i = 0; i < phnum; i++) {
        var phdr = phdrs.add(i * phentsize);
        var p_type = phdr.readU32();

        // if p_type is 0 check if it's really 0 from the file
        var phdrs_from_file = null;
        if (p_type === 0 && fd != null && fd !== -1) {
            phdrs_from_file = Memory.alloc(phnum * phentsize);
            lseek(fd, phoff, SEEK_SET);
            read(fd, phdrs_from_file, phnum * phentsize);
            p_type = phdrs_from_file.add(i * phentsize).readU32();
        }
        var p_type_sym = null;

        // check if p_type matches the defined p_type
        var p_type_exists = false;
        for (let key in p_types) {
            if (p_types[key] === p_type) {
                p_type_exists = true;
                p_type_sym = key;
                break;
            }
        }
        if (!p_type_exists) break;

        var p_offset = is32bit ? phdr.add(0x4).readU32() : phdr.add(0x8).readU64();
        var p_vaddr = is32bit ? phdr.add(0x8).readU32() : phdr.add(0x10).readU64();
        var p_paddr = is32bit ? phdr.add(0xc).readU32() : phdr.add(0x18).readU64();
        var p_filesz = is32bit ? phdr.add(0x10).readU32() : phdr.add(0x20).readU64();
        var p_memsz = is32bit ? phdr.add(0x14).readU32() : phdr.add(0x28).readU64();
        var p_flags = is32bit ? phdr.add(0x18).readU32() : phdr.add(0x4).readU32();
        var p_align = is32bit ? phdr.add(0x1c).readU32() : phdr.add(0x30).readU64();
        // console.log(`p_type: ${p_type}, p_offset: ${p_offset}, p_vaddr: ${p_vaddr}, p_paddr: ${p_paddr}, p_filesz: ${p_filesz}, p_memsz: ${p_memsz}, p_flags: ${p_flags}, p_align: {p_align}`);

        // if p_flags is 0, check it from the file
        if (p_flags === 0 && fd != null && fd !== -1) {
            phdrs_from_file = Memory.alloc(phnum * phentsize);
            lseek(fd, phoff, SEEK_SET);
            read(fd, phdrs_from_file, phnum * phentsize);
            var phdr_from_file = phdrs_from_file.add(i * phentsize);
            p_offset = is32bit ? phdr_from_file.add(0x4).readU32() : phdr_from_file.add(0x8).readU64();
            p_vaddr = is32bit ? phdr_from_file.add(0x8).readU32() : phdr_from_file.add(0x10).readU64();
            p_paddr = is32bit ? phdr_from_file.add(0xc).readU32() : phdr_from_file.add(0x18).readU64();
            p_filesz = is32bit ? phdr_from_file.add(0x10).readU32() : phdr_from_file.add(0x20).readU64();
            p_memsz = is32bit ? phdr_from_file.add(0x14).readU32() : phdr_from_file.add(0x28).readU64();
            p_flags = is32bit ? phdr_from_file.add(0x18).readU32() : phdr_from_file.add(0x4).readU32();
            p_align = is32bit ? phdr_from_file.add(0x1c).readU32() : phdr_from_file.add(0x30).readU64();
        }

        // .rodata section
        if (p_type_sym === 'PT_LOAD' && p_vaddr == 0) {
            PT_LOAD_rodata_p_memsz = p_memsz;
            continue;
        }

        // .text section
        if (p_type_sym === 'PT_LOAD' && p_vaddr != 0) {
            if (PT_LOAD_text_p_vaddr == null && PT_LOAD_text_p_memsz == null) {
                PT_LOAD_text_p_vaddr = p_vaddr;
                PT_LOAD_text_p_memsz = p_memsz;
            }
            continue;
        }

        if (p_type_sym === 'PT_GNU_RELRO') {
            PT_GNU_RELRO_p_vaddr = p_vaddr;
            PT_GNU_RELRO_p_memsz = p_memsz;
            break;
        }
    }
}
/* Parsing elf function */

/* Parsing MachO function */
function parseMachO(base) {
    base = ptr(base)
    var magic = base.readU32();
    var is64bit = false;
    if (magic == 0xfeedfacf) {
        is64bit = true;
        var number_of_commands_offset = 0x10
        var command_size_offset = 0x4
        var segment_name_offset = 0x8
        var vm_address_offset = 0x18
        var vm_size_offset = 0x20
        var file_offset = 0x28
        var number_of_sections_offset = 0x40
        var section64_header_base_offset = 0x48
        var section64_header_size = 0x50
    } else {
        console.log('Unknown magic:' + magic);
    }
    var cmdnum = base.add(number_of_commands_offset).readU32();
    // send({'parseMachO': {'cmdnum': cmdnum}})
    var cmdoff = is64bit ? 0x20 : 0x1C;
    for (var i = 0; i < cmdnum; i++) {
        var cmd = base.add(cmdoff).readU32();
        var cmdsize = base.add(cmdoff + command_size_offset).readU32();
        if (cmd === 0x19) { // SEGMENT_64
            var segname = base.add(cmdoff + segment_name_offset).readUtf8String();
            var vmaddr = base.add(cmdoff + vm_address_offset).readU32();
            var vmsize = base.add(cmdoff + vm_size_offset).readU32();
            var fileoffset = base.add(cmdoff + file_offset).readU32();
            var nsects = base.add(cmdoff + number_of_sections_offset).readU8();
            var secbase = base.add(cmdoff + section64_header_base_offset);

            if (base.add(cmdoff + command_size_offset).readU32() >= section64_header_base_offset + nsects * section64_header_size) {
                var TEXT_segment_text_section_index = 0;
                var TEXT_segment_cstring_section_index = 0;
                var DATA_segment_const_section_index = 0;
                for (var i = 0; i < nsects; i++) {
                    var secname = secbase.add(i * section64_header_size).readUtf8String()
                    var section_start_offset = secbase.add(i * section64_header_size + 0x30).readU32();                        

                    if (segname === '__TEXT' && secname === '__text') {
                        TEXT_segment_text_section_index = i;
                        TEXT_segment_text_section_offset = section_start_offset;
                    } else if (segname === '__TEXT' && i == (TEXT_segment_text_section_index + 1)) {
                        TEXT_segment_text_section_size = section_start_offset - TEXT_segment_text_section_offset;
                    } else if (segname === '__TEXT' && secname === '__cstring') {
                        TEXT_segment_cstring_section_index = i;
                        TEXT_segment_cstring_section_offset = section_start_offset;
                    } else if (segname === '__TEXT' && i == (TEXT_segment_cstring_section_index + 1)) {
                        TEXT_segment_cstring_section_size = section_start_offset - TEXT_segment_cstring_section_offset;
                    } else if (segname === '__DATA' && secname === '__const') {
                        DATA_segment_const_section_index = i;
                        DATA_segment_const_section_offset = section_start_offset;
                    } else if (segname === '__DATA' && i == (DATA_segment_const_section_index + 1)) {
                        DATA_segment_const_section_size = section_start_offset - DATA_segment_const_section_offset;
                    }
                }
            }
        }
        cmdoff += cmdsize;
    }
}
/* Parsing MachO function */

/* Hook flutter engine function to capture the network traffic */
function hook(target) {
    if (target == "GetSockAddr") {
        // Hook SocketAddress::GetSockAddr function so we can get the address of sockaddr structure
        Interceptor.attach(GetSockAddr_func_addr, {
            onEnter: function(args) { 
                // console.log(`[!] sockaddr: ${args[1]}`);
                sockaddr = args[1];
            },
            onLeave: function(retval) {}
        })
        // Hook the socket function and replace the IP and port with our burp ones.
        Interceptor.attach(Module.getGlobalExportByName("socket"), {
            onEnter: function(args) {
                // AF_INET(IPv4) == 2, AF_INET6(IPv6) == 10
                var overwrite = false;
                if (Process.platform === 'linux' && sockaddr != null && ptr(sockaddr).readU16() == 2) {
                    overwrite = true;
                }
                else if (Process.platform === 'darwin' && sockaddr != null && ptr(sockaddr).add(0x1).readU8() == 2) {
                    overwrite = true;
                }

                if (overwrite) {
                    console.log(`[*] Overwrite sockaddr as our burp proxy ip and port --> ${BURP_PROXY_IP}:${BURP_PROXY_PORT}`);
                    ptr(sockaddr).add(0x2).writeU16(byteFlip(BURP_PROXY_PORT));
                    ptr(sockaddr).add(0x4).writeByteArray(convertIpToByteArray(BURP_PROXY_IP));
                }
            },
            onLeave: function(retval) {}
        })
    }
    else if (target == "verifyCertChain") {
        // Hook the verify_cert_chain function and replace the return value with true, so we can capture ssl traffic
        Interceptor.attach(verify_cert_chain_func_addr, {
            onEnter: function(args) {},
            onLeave: function(retval) {
                if (retval == "0x0") {
                    console.log(`[*] verify cert bypass`);
                    var newretval = ptr(0x1);
                    retval.replace(newretval);
                }
            }
        })
    }
    else if (target == "verifyPeerCert") {
        // Hook the verify_peer_cert function and replace it, so we can capture ssl traffic
        // https://github.com/NVISOsecurity/disable-flutter-tls-verification/blob/ecc6e9ed9e1182645b32da68d7be6aefb2b7e970/disable-flutter-tls.js#L151
        Interceptor.replace(verify_peer_cert_func_addr, new NativeCallback((pathPtr, flags) => {
            console.log(`[*] verify peer cert bypass`);
            return 0;
        }, 'int', ['pointer', 'int']));
    }
}
/* Hook flutter engine function to capture the network traffic */

/* main */
var target_flutter_library = ObjC.available ? "Flutter.framework/Flutter" : (Java.available ? "libflutter.so" : null);
if (target_flutter_library != null) { 
    var awaitForCondition = function(callback) {
        var module_loaded = 0;
        var base = null;
        var int = setInterval(function() {
            Process.enumerateModules()
            .filter(function(m){ return m['path'].indexOf(target_flutter_library) != -1; })
            .forEach(function(m) {
                if (ObjC.available) {
                    target_flutter_library = target_flutter_library.split('/').pop();
                }
                console.log(`[*] ${target_flutter_library} loaded!`);
                base = Process.getModuleByName(target_flutter_library).base;
                return module_loaded = 1;
            })
            if(module_loaded) {
                clearInterval(int);
                callback(+base);
                return;
            }
        }, 0);
    }
    
    function init(base) {
        flutter_base = ptr(base);
        console.log(`[*] ${target_flutter_library} base: ${flutter_base}`);
        if (Process.platform === 'linux') {
            appId = findAppId();
            console.log(`[*] package name: ${appId}`);    
        }

        var ssl_client_string = '73 73 6C 5F 63 6C 69 65 6E 74 00';
        var Socket_CreateConnect_string = '53 6f 63 6b 65 74 5f 43 72 65 61 74 65 43 6f 6e 6e 65 63 74 00';
        // "third_party/boringssl/src/ssl/handshake.cc" string. First, Scan this string and then need to find the start address of "../../"
        var handshake_string = '74 68 69 72 64 5f 70 61 72 74 79 2f 62 6f 72 69 6e 67 73 73 6c 2f 73 72 63 2f 73 73 6c 2f 68 61 6e 64 73 68 61 6b 65 2e 63 63';
        if (Process.platform === 'linux') {
            parseElf(flutter_base);
            if (PT_LOAD_rodata_p_memsz != null) {
                // "ssl_client" string scan from the libflutter base address to the right before the .text section
                scanMemory(flutter_base, PT_LOAD_rodata_p_memsz, ssl_client_string, "ssl_client");
                // "Socket_CreateConnect" string scan
                scanMemory(flutter_base, PT_LOAD_rodata_p_memsz, Socket_CreateConnect_string, "Socket_CreateConnect");
            }
        } 
        else if (Process.platform === 'darwin') {
            parseMachO(flutter_base);
            // Find verify_peer_cert function address by scanning "third_party/boringssl/src/ssl/handshake.cc" string
            scanMemory(flutter_base.add(TEXT_segment_cstring_section_offset), TEXT_segment_cstring_section_size, handshake_string, "handshake");
            scanMemory(flutter_base.add(TEXT_segment_cstring_section_offset), TEXT_segment_cstring_section_size, Socket_CreateConnect_string, "Socket_CreateConnect");
        }
    
        var int_getSockAddr = setInterval(() => {
            if (GetSockAddr_func_addr != null) {
                console.log("[*] Hook GetSockAddr function");
                hook("GetSockAddr");
                clearInterval(int_getSockAddr);
            }
        }, 0);
        
        if (Process.platform === 'linux') {
            var int_verifyCertBypass = setInterval(() => {
                if (verify_cert_chain_func_addr != null) {
                    console.log("[*] Hook verify_cert_chain function");
                    hook("verifyCertChain");
                    clearInterval(int_verifyCertBypass);
                }
            }, 0);
        }
        // On iOS, hooking verify_cert_chain doesn't work. Instead, hook verify_peer_cert
        else if (Process.platform === 'darwin') {
            var int_verifyPeerCertBypass = setInterval(() => {
                if (verify_peer_cert_func_addr != null) {
                    console.log("[*] Hook verify_peer_cert function");
                    hook("verifyPeerCert");
                    clearInterval(int_verifyPeerCertBypass);
                }
            }, 0);
        }
    }

    BURP_PROXY_IP = "10.0.2.2";
    BURP_PROXY_PORT = 8080;

    awaitForCondition(init);
}
/* main */

Util

String <-> Binary

function bin2String(bArr) {
	var JString = Java.use('java.lang.String');
	var str = JString.$new(bArr).toString();

	return str
}

function string2Bin(str) {
    var result = [];
    for (var i = 0; i < str.length; i++) {
      result.push(str.charCodeAt(i));
    }
    return result;
}

ByteString <-> String

// if(byteString!=null) Bytes = byteString.getData$okio();
var Bytes = "okio.ByteString의 Byte 배열" 
var JavaByte = Java.use("[B");
var buffer = Java.cast(Bytes, JavaByte);
var result1 = Java.array('byte', buffer);
var JString = Java.use('java.lang.String');
var str = JString.$new(result1);

let result = "";

// String -> Bytearray
var newStr = `{"pin_txid":"string"}`
var newbyteString = string2Bin(newStr)

var java_bytestr = Java.use('okio.ByteString').$new(newbyteString)
result = this[method](mediaType, java_bytestr);

OnClickListen

function OnClickListener() {
    Java.perform(function () {
        Java.use("android.view.View").setOnClickListener.implementation = function (
            listener
            ) {
            if (listener != null) {
                watch(listener, "onClick");
            }
            return this.setOnClickListener(listener);
        };

        Java.choose("android.view.View$ListenerInfo", {
            onMatch: function (instance) {
                instance = instance.mOnClickListener.value;
                if (instance) {
                console.log("mOnClickListener name is :" + getObjClassName(instance));
                watch(instance, "onClick");
                }
            },
            onComplete: function () {},
        });
    });
}

AlertDialog Prevent

Java.perform(function(){
    var AD = Java.use('android.app.AlertDialog')
    AD.show.implementation = function (){
        console.log("Prevent Alert")
    }
})

Shared Preferences

function notifyNewSharedPreference() {
	Java.use('android.app.SharedPreferencesImpl$EditorImpl').putString.overload('java.lang.String', 'java.lang.String').implementation = function(k, v) {
        console.log('[SharedPreferencesImpl]', k, '=', v);
        return this.putString(k, v);
    }
}   

SQLite Log

Interceptor.attach(Module.findExportByName('libsqlite.so', 'sqlite3_prepare16_v2'), {
      onEnter: function(args) {
          console.log('DB: ' + Memory.readUtf16String(args[0]) + '\tSQL: ' + Memory.readUtf16String(args[1]));
      }
});

Hook First Instance

Java.perform(function() {
    Java.choose("com.examplePackage.exampleClass",
    {
        onMatch: function(instance)
        {
            console.log("[+] Instance Found! Hook Start");
        },    
        onComplete: function()
        {
            console.log("[*] Instance Finished");
        }
    });
});

Reflection

function reflectFunctionCall(){
    Java.perform(function() {
        Java.use("java.lang.Class").forName.overload('java.lang.String').implementation = function (param0) {
            console.log("forName hook : " + param0);
            return this.forName(param0);
        };
        
        Java.use("java.lang.Class").getMethod.overload('java.lang.String', '[Ljava.lang.Class;').implementation = function (param0, param1) {
            console.log("getMethod hook : " + param0);
            return this.getMethod(param0, param1);
        };
        
        var jlrmethod = Java.use("java.lang.reflect.Method")
        jlrmethod.invoke.implementation = function(object, parameters){
            var retvalue = this.invoke(object, parameters)
        
            var type = Object.prototype.toString.call(parameters)
            if(type == '[object Array]'){
                var arrayLength = parameters.length;
                // console.log(arrayLength)
                for (var i = 0; i < arrayLength; i++) {
                    var parameter = parameters[i]
                    var paramstring = String(parameter)
                    if(paramstring.startsWith("[Ljava.lang.String;@")){
                        console.log("------------")
                        console.log("string array")
                        console.log(type + JSON.stringify(parameter))
                        
                        var result = parameter
                        console.log(type + result)
                        console.log("------------")
                    }
                    else{
                        console.log(type + parameter)
                    }
                }
            }
            else{
                console.log(type + parameters)
            }
            console.log("Method.invoke(object, parameters): '" + this.toString() + "' = '" + object + "', '" + parameters + "' = '" + retvalue + "'")
            if(retvalue=='Invalid'){
                return 'Invalid'
            }
            else if(this.toString().includes('bE23766.a()')&& retvalue=='INVALID'){
                console.log("###################"+JSON.stringify(retvalue))
            }
            
            return retvalue
        }
        
    })
}

Describe Class

function describeJavaClass(className) {
  var jClass = Java.use(className);
  console.log(JSON.stringify({
    _name: className,
    _methods: Object.getOwnPropertyNames(jClass.__proto__).filter(m => {
      return !m.startsWith('$') // filter out Frida related special properties
         || m == 'class' || m == 'constructor' // optional
    }), 
    _fields: jClass.class.getFields().map(f => {
      return f.toString()
    })  
  }, null, 2));
}