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


Android 진단 환경 구축
ADB
SDK 설치 (Android Studio 설치하면 알아서 깔림)
- https://developer.android.com/studio/releases/platform-tools
- SDK 설치 후 platform-tools 디렉토리 환경 변수 등록
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에서 인증서 설치 진행
높은 버전의 경우
- Burp Suite [Proxy] - [Options] - [Import/export certificate] 기능을 통해 인증서 파일을 추출
-
openssl x509 -inform DER -in cacert.cer -out cacert.pempem 변환 -
openssl x509 -inform PEM -subject_hash_old -in cacert.pem해쉬값 추출 -
mv cacert.pem 9a5ba575.0파일명 해쉬값으로 변경 adb push 9a5ba575.0 /data/local/tmp-
mount -o rw,remount /쓰기권한 마운트 시키기 -
cp 9a5ba575.0 /system/etc/security/cacerts/ls -l /system/etc/security/cacerts/ | grep 9a5ba575
mount -o ro,remount /- [설정] - [생체 인식 및 보안] - [기타 보안 설정] - [인증서 확인] 에서 확인
정적 분석
APK 추출
- ASTRO 백업 기능 이용
- 플레이스토어에서 APK 다운로드
- APK Extractor 이용
디컴파일
- jadx APK 파일 그냥 jadx에 넣으면 됨
- APK Tool 이용
https://github.com/iBotPeaches/Apktool/releases/tag/v2.8.0
java -jar apktool_2.8.0.jar d "디컴파일 할 앱 이름"
- 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
- Manifest에서 debugging이 되는지 확인
- 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
/* 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));
}