From 7a00e8fc583de0862732b93d5278666f2319827f Mon Sep 17 00:00:00 2001 From: Beau Kujath Date: Wed, 5 Apr 2023 12:08:32 -0600 Subject: [PATCH] adding re env setup --- README.md | 54 +- frida/__handlers__/libc.so/open.js | 38 + frida/__handlers__/libc.so/send.js | 38 + frida/__handlers__/libc.so/sendfile.js | 38 + frida/__handlers__/libc.so/sendmmsg.js | 38 + frida/__handlers__/libc.so/sendmsg.js | 38 + frida/__handlers__/libc.so/sendto.js | 38 + frida/col_pin_pass.sh | 11 + frida/frida_scripts/classers.js | 28 + frida/frida_scripts/enumerate_methods.js | 5 + frida/frida_scripts/file_access.js | 455 ++++++++++++ frida/frida_scripts/frida_certpin_pass.js | 66 ++ frida/frida_scripts/hook-finder.js | 251 +++++++ frida/frida_scripts/http-connection.js | 148 ++++ frida/frida_scripts/multiple_unpinning.js | 731 ++++++++++++++++++++ frida/frida_scripts/other-enumerate.js | 1 + frida/frida_scripts/pass2.js | 523 ++++++++++++++ frida/frida_scripts/printStrings.js | 38 + frida/frida_scripts/raptor_android_enum.js | 105 +++ frida/frida_scripts/raptor_android_trace.js | 183 +++++ frida/frida_scripts/ssl_pinning.js | 70 ++ frida/frida_scripts/trace.js | 3 + frida/frida_start.sh | 11 + frida/get_frida_server.sh | 25 + frida/install_frida.sh | 17 + frida/tracers/trace_opens.sh | 7 + frida/tracers/trace_recvs.sh | 7 + frida/tracers/trace_sends.sh | 7 + grab_apk.sh | 24 + install_all.sh | 23 + jadx/install_jadx.sh | 20 + setup_scripts/install_mobsf.sh | 15 + setup_scripts/install_prereqs.sh | 17 + setup_scripts/make_root_ca.sh | 24 + start_all.sh | 13 + stop_all.sh | 14 + 36 files changed, 3123 insertions(+), 1 deletion(-) create mode 100644 frida/__handlers__/libc.so/open.js create mode 100644 frida/__handlers__/libc.so/send.js create mode 100644 frida/__handlers__/libc.so/sendfile.js create mode 100644 frida/__handlers__/libc.so/sendmmsg.js create mode 100644 frida/__handlers__/libc.so/sendmsg.js create mode 100644 frida/__handlers__/libc.so/sendto.js create mode 100755 frida/col_pin_pass.sh create mode 100644 frida/frida_scripts/classers.js create mode 100644 frida/frida_scripts/enumerate_methods.js create mode 100644 frida/frida_scripts/file_access.js create mode 100644 frida/frida_scripts/frida_certpin_pass.js create mode 100644 frida/frida_scripts/hook-finder.js create mode 100644 frida/frida_scripts/http-connection.js create mode 100644 frida/frida_scripts/multiple_unpinning.js create mode 100644 frida/frida_scripts/other-enumerate.js create mode 100644 frida/frida_scripts/pass2.js create mode 100644 frida/frida_scripts/printStrings.js create mode 100644 frida/frida_scripts/raptor_android_enum.js create mode 100644 frida/frida_scripts/raptor_android_trace.js create mode 100644 frida/frida_scripts/ssl_pinning.js create mode 100644 frida/frida_scripts/trace.js create mode 100755 frida/frida_start.sh create mode 100755 frida/get_frida_server.sh create mode 100755 frida/install_frida.sh create mode 100755 frida/tracers/trace_opens.sh create mode 100755 frida/tracers/trace_recvs.sh create mode 100755 frida/tracers/trace_sends.sh create mode 100755 grab_apk.sh create mode 100755 install_all.sh create mode 100755 jadx/install_jadx.sh create mode 100755 setup_scripts/install_mobsf.sh create mode 100755 setup_scripts/install_prereqs.sh create mode 100755 setup_scripts/make_root_ca.sh create mode 100755 start_all.sh create mode 100755 stop_all.sh diff --git a/README.md b/README.md index d314398..3612f1f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,55 @@ # relab +Lab for reverse engineering APKs - RE env for inspecting APKs \ No newline at end of file + +### RE environment for dynamic and static analysis of APKs + +Includes: + +1. mitmproxy +2. Frida +3. Genymotion +4. Jadx +5. apktool + +_**Note:** Setup scripts built and tested on Ubuntu 20_ + +#### Prereqs: + +1. Python3: `sudo apt install python3` +2. pip3: `sudo apt install python3-pip` +3. dev-tools: `apt install build-essential`) + + +#### Install Dynamic Analysis Tools + +1. Run install script for mitmproxy and genymotion emulator: `./install_all.sh` +2. Create and start Android emulated device in Genymotion OR attach physical rooted test Android device over USB. +3. Make sure test device is accessible over adb with root access: `adb shell` -> `su` +4. Run script to copy mitmproxy cert to be system cert on device: `cd setup_scripts; ./make_root_ca.sh` +5. Install frida: `cd frida; ./install_frida.sh` +6. Get frida-server binary then push to test Android device: `./get_frida_server.sh` +7. Start frida-server on Android: `adb shell` -> `su` -> `/data/local/tmp/frida-server &` +8. Verify frida is attaching to device over adb: `frida-ps -U` + + +_**Note:**_ May need to mount Android filesystem as writable after step 3: `adb shell; su; mount -o rw,remount /system` + +#### Capturing Live HTTPS from app + +1. Start mitmproxy on desktop: `cd mitmprox; ./mitmweb` +2. Make sure test Android is connected to proxy: `Settings` -> `Network` -> `Wi-Fi` -> `Click then hold down connected network` -> `Modify network` -> `(click) Advanced options drop down` -> `Set Proxy to "Manual"` -> `hostname = IP of desktop` -> `proxy port = 8080` +3. View decrypted traffic panel in `mitmweb` browser on desktop at: `localhost:8081 +4. Visit any site in browser on Android to verify decryption is working + + + +#### Use Frida to bypass SSL pinning and capture files accessed + +1. Make sure frida server is started on Android and verify connection: `frida-ps -U` +2. Find name of app package to target with frida: `adb shell pm list packages` +3. Bypass SSL for targeted app: `frida -l frida_scripts/multiple_unpinning.js -U -f --no-pause` +4. Trace files being opened by app on device: `frida-trace -U -i open -f ` + + +_**Note:** Most Android apps do not need SSL pinning bypass for mitmproxy to work_ diff --git a/frida/__handlers__/libc.so/open.js b/frida/__handlers__/libc.so/open.js new file mode 100644 index 0000000..aa5603f --- /dev/null +++ b/frida/__handlers__/libc.so/open.js @@ -0,0 +1,38 @@ +/* + * Auto-generated by Frida. Please modify to match the signature of open. + * This stub is currently auto-generated from manpages when available. + * + * For full API reference, see: https://frida.re/docs/javascript-api/ + */ + +{ + /** + * Called synchronously when about to call open. + * + * @this {object} - Object allowing you to store state for use in onLeave. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {array} args - Function arguments represented as an array of NativePointer objects. + * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8. + * It is also possible to modify arguments by assigning a NativePointer object to an element of this array. + * @param {object} state - Object allowing you to keep state across function calls. + * Only one JavaScript function will execute at a time, so do not worry about race-conditions. + * However, do not use this to store function arguments across onEnter/onLeave, but instead + * use "this" which is an object for keeping state local to an invocation. + */ + onEnter(log, args, state) { + log(`open(pathname="${args[0].readUtf8String()}", flags=${args[1]})`); + }, + + /** + * Called synchronously when about to return from open. + * + * See onEnter for details. + * + * @this {object} - Object allowing you to access state stored in onEnter. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {NativePointer} retval - Return value represented as a NativePointer object. + * @param {object} state - Object allowing you to keep state across function calls. + */ + onLeave(log, retval, state) { + } +} diff --git a/frida/__handlers__/libc.so/send.js b/frida/__handlers__/libc.so/send.js new file mode 100644 index 0000000..4901eea --- /dev/null +++ b/frida/__handlers__/libc.so/send.js @@ -0,0 +1,38 @@ +/* + * Auto-generated by Frida. Please modify to match the signature of send. + * This stub is currently auto-generated from manpages when available. + * + * For full API reference, see: https://frida.re/docs/javascript-api/ + */ + +{ + /** + * Called synchronously when about to call send. + * + * @this {object} - Object allowing you to store state for use in onLeave. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {array} args - Function arguments represented as an array of NativePointer objects. + * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8. + * It is also possible to modify arguments by assigning a NativePointer object to an element of this array. + * @param {object} state - Object allowing you to keep state across function calls. + * Only one JavaScript function will execute at a time, so do not worry about race-conditions. + * However, do not use this to store function arguments across onEnter/onLeave, but instead + * use "this" which is an object for keeping state local to an invocation. + */ + onEnter(log, args, state) { + log(`send(sockfd=${args[0]}, buf=${args[1]}, len=${args[2]}, flags=${args[3]})`); + }, + + /** + * Called synchronously when about to return from send. + * + * See onEnter for details. + * + * @this {object} - Object allowing you to access state stored in onEnter. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {NativePointer} retval - Return value represented as a NativePointer object. + * @param {object} state - Object allowing you to keep state across function calls. + */ + onLeave(log, retval, state) { + } +} diff --git a/frida/__handlers__/libc.so/sendfile.js b/frida/__handlers__/libc.so/sendfile.js new file mode 100644 index 0000000..2de1a9c --- /dev/null +++ b/frida/__handlers__/libc.so/sendfile.js @@ -0,0 +1,38 @@ +/* + * Auto-generated by Frida. Please modify to match the signature of sendfile. + * This stub is currently auto-generated from manpages when available. + * + * For full API reference, see: https://frida.re/docs/javascript-api/ + */ + +{ + /** + * Called synchronously when about to call sendfile. + * + * @this {object} - Object allowing you to store state for use in onLeave. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {array} args - Function arguments represented as an array of NativePointer objects. + * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8. + * It is also possible to modify arguments by assigning a NativePointer object to an element of this array. + * @param {object} state - Object allowing you to keep state across function calls. + * Only one JavaScript function will execute at a time, so do not worry about race-conditions. + * However, do not use this to store function arguments across onEnter/onLeave, but instead + * use "this" which is an object for keeping state local to an invocation. + */ + onEnter(log, args, state) { + log(`sendfile(out_fd=${args[0]}, in_fd=${args[1]}, offset=${args[2]}, count=${args[3]})`); + }, + + /** + * Called synchronously when about to return from sendfile. + * + * See onEnter for details. + * + * @this {object} - Object allowing you to access state stored in onEnter. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {NativePointer} retval - Return value represented as a NativePointer object. + * @param {object} state - Object allowing you to keep state across function calls. + */ + onLeave(log, retval, state) { + } +} diff --git a/frida/__handlers__/libc.so/sendmmsg.js b/frida/__handlers__/libc.so/sendmmsg.js new file mode 100644 index 0000000..2dfd457 --- /dev/null +++ b/frida/__handlers__/libc.so/sendmmsg.js @@ -0,0 +1,38 @@ +/* + * Auto-generated by Frida. Please modify to match the signature of sendmmsg. + * This stub is currently auto-generated from manpages when available. + * + * For full API reference, see: https://frida.re/docs/javascript-api/ + */ + +{ + /** + * Called synchronously when about to call sendmmsg. + * + * @this {object} - Object allowing you to store state for use in onLeave. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {array} args - Function arguments represented as an array of NativePointer objects. + * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8. + * It is also possible to modify arguments by assigning a NativePointer object to an element of this array. + * @param {object} state - Object allowing you to keep state across function calls. + * Only one JavaScript function will execute at a time, so do not worry about race-conditions. + * However, do not use this to store function arguments across onEnter/onLeave, but instead + * use "this" which is an object for keeping state local to an invocation. + */ + onEnter(log, args, state) { + log(`sendmmsg(sockfd=${args[0]}, msgvec=${args[1]}, vlen=${args[2]}, flags=${args[3]})`); + }, + + /** + * Called synchronously when about to return from sendmmsg. + * + * See onEnter for details. + * + * @this {object} - Object allowing you to access state stored in onEnter. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {NativePointer} retval - Return value represented as a NativePointer object. + * @param {object} state - Object allowing you to keep state across function calls. + */ + onLeave(log, retval, state) { + } +} diff --git a/frida/__handlers__/libc.so/sendmsg.js b/frida/__handlers__/libc.so/sendmsg.js new file mode 100644 index 0000000..476a6ad --- /dev/null +++ b/frida/__handlers__/libc.so/sendmsg.js @@ -0,0 +1,38 @@ +/* + * Auto-generated by Frida. Please modify to match the signature of sendmsg. + * This stub is currently auto-generated from manpages when available. + * + * For full API reference, see: https://frida.re/docs/javascript-api/ + */ + +{ + /** + * Called synchronously when about to call sendmsg. + * + * @this {object} - Object allowing you to store state for use in onLeave. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {array} args - Function arguments represented as an array of NativePointer objects. + * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8. + * It is also possible to modify arguments by assigning a NativePointer object to an element of this array. + * @param {object} state - Object allowing you to keep state across function calls. + * Only one JavaScript function will execute at a time, so do not worry about race-conditions. + * However, do not use this to store function arguments across onEnter/onLeave, but instead + * use "this" which is an object for keeping state local to an invocation. + */ + onEnter(log, args, state) { + log(`sendmsg(sockfd=${args[0]}, msg=${args[1]}, flags=${args[2]})`); + }, + + /** + * Called synchronously when about to return from sendmsg. + * + * See onEnter for details. + * + * @this {object} - Object allowing you to access state stored in onEnter. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {NativePointer} retval - Return value represented as a NativePointer object. + * @param {object} state - Object allowing you to keep state across function calls. + */ + onLeave(log, retval, state) { + } +} diff --git a/frida/__handlers__/libc.so/sendto.js b/frida/__handlers__/libc.so/sendto.js new file mode 100644 index 0000000..1a88c35 --- /dev/null +++ b/frida/__handlers__/libc.so/sendto.js @@ -0,0 +1,38 @@ +/* + * Auto-generated by Frida. Please modify to match the signature of sendto. + * This stub is currently auto-generated from manpages when available. + * + * For full API reference, see: https://frida.re/docs/javascript-api/ + */ + +{ + /** + * Called synchronously when about to call sendto. + * + * @this {object} - Object allowing you to store state for use in onLeave. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {array} args - Function arguments represented as an array of NativePointer objects. + * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8. + * It is also possible to modify arguments by assigning a NativePointer object to an element of this array. + * @param {object} state - Object allowing you to keep state across function calls. + * Only one JavaScript function will execute at a time, so do not worry about race-conditions. + * However, do not use this to store function arguments across onEnter/onLeave, but instead + * use "this" which is an object for keeping state local to an invocation. + */ + onEnter(log, args, state) { + log(`sendto(sockfd=${args[0]}, buf=${args[1]}, len=${args[2]}, flags=${args[3]}, dest_addr=${args[4]}, addrlen=${args[5]})`); + }, + + /** + * Called synchronously when about to return from sendto. + * + * See onEnter for details. + * + * @this {object} - Object allowing you to access state stored in onEnter. + * @param {function} log - Call this function with a string to be presented to the user. + * @param {NativePointer} retval - Return value represented as a NativePointer object. + * @param {object} state - Object allowing you to keep state across function calls. + */ + onLeave(log, retval, state) { + } +} diff --git a/frida/col_pin_pass.sh b/frida/col_pin_pass.sh new file mode 100755 index 0000000..3e19197 --- /dev/null +++ b/frida/col_pin_pass.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# + + +printf "\n\nBypassing ssl pin in Claro colombia" + + +frida -l frida_scripts/multiple_unpinning -U -f com.clarocolombia.miclaro --no-pause + + + diff --git a/frida/frida_scripts/classers.js b/frida/frida_scripts/classers.js new file mode 100644 index 0000000..82ebe30 --- /dev/null +++ b/frida/frida_scripts/classers.js @@ -0,0 +1,28 @@ +Java.perform(() => { + + var obj = Java.enumerateMethods('*com.twitter*!*') // com.example.demotest is application package name + console.log("obj length: " + obj.length); +///var methods= JSON.stringify(groups, null," ") +///console.log(methods) + +///var common =JSON.stringify(obj[0].classes[0],null," ") +///console.log("\x1b[32m","class-name "+" "+JSON.parse(common).name,"\n"+"\x1b[35m","Methods name"+" "+JSON.parse(common).methods) +///console.log("\x1b[32m", common); +var i =0; +//console.log(obj.length) +for (i=0;i"+x) + //console.log("df") +console.log("\x1b[34m",JSON.parse(common).methods[x]) + + +} + +} +}); diff --git a/frida/frida_scripts/enumerate_methods.js b/frida/frida_scripts/enumerate_methods.js new file mode 100644 index 0000000..65aeb10 --- /dev/null +++ b/frida/frida_scripts/enumerate_methods.js @@ -0,0 +1,5 @@ +console.log("Enumerating methods of MainActivity"); +Java.perform(function() { + const groups = Java.enumerateMethods('*com.movistarmx*!*'); + console.log(JSON.stringify(groups, null, 2)); +}); diff --git a/frida/frida_scripts/file_access.js b/frida/frida_scripts/file_access.js new file mode 100644 index 0000000..be4a1ab --- /dev/null +++ b/frida/frida_scripts/file_access.js @@ -0,0 +1,455 @@ +/** +It should be launch earlier in order to be aware of a maximun +quantity of file descriptors. + + +@author @FrenchYeti +*/ +Java.perform(function() { + + // ============= Config + var CONFIG = { + // if TRUE enable data dump + printEnable: true, + // if TRUE enable libc.so open/read/write hook + printLibc: false, + // if TRUE print the stack trace for each hook + printStackTrace: false, + // to filter the file path whose data want to be dumped in ASCII + dump_ascii_If_Path_contains: [".log", ".xml", ".prop"], + // to filter the file path whose data want to be NOT dumped in hexdump (useful for big chunk and excessive reads) + dump_hex_If_Path_NOT_contains: [".png", "/proc/self/task", "/system/lib", "base.apk", "cacert"], + // to filter the file path whose data want to be NOT dumped fron libc read/write (useful for big chunk and excessive reads) + dump_raw_If_Path_NOT_contains: [".png", "/proc/self/task", "/system/lib", "base.apk", "cacert"] + } + + // ============= Keep a trace of file descriptor, path, and so + var TraceFD = {}; + var TraceFS = {}; + var TraceFile = {}; + var TraceSysFD = {}; + + + // ============= Get classes + var CLS = { + File: Java.use("java.io.File"), + FileInputStream: Java.use("java.io.FileInputStream"), + FileOutputStream: Java.use("java.io.FileOutputStream"), + String: Java.use("java.lang.String"), + FileChannel: Java.use("java.nio.channels.FileChannel"), + FileDescriptor: Java.use("java.io.FileDescriptor"), + Thread: Java.use("java.lang.Thread"), + StackTraceElement: Java.use("java.lang.StackTraceElement"), + AndroidDbSQLite: Java.use("android.database.sqlite.SQLiteDatabase") + }; + var File = { + new: [ + CLS.File.$init.overload("java.io.File", "java.lang.String"), + CLS.File.$init.overload("java.lang.String"), + CLS.File.$init.overload("java.lang.String", "java.lang.String"), + CLS.File.$init.overload("java.net.URI"), + ] + }; + var FileInputStream = { + new: [ + CLS.FileInputStream.$init.overload("java.io.File"), + CLS.FileInputStream.$init.overload("java.io.FileDescriptor"), + CLS.FileInputStream.$init.overload("java.lang.String"), + ], + read: [ + CLS.FileInputStream.read.overload(), + CLS.FileInputStream.read.overload("[B"), + CLS.FileInputStream.read.overload("[B", "int", "int"), + ], + }; + var FileOuputStream = { + new: [ + CLS.FileOutputStream.$init.overload("java.io.File"), + CLS.FileOutputStream.$init.overload("java.io.File", "boolean"), + CLS.FileOutputStream.$init.overload("java.io.FileDescriptor"), + CLS.FileOutputStream.$init.overload("java.lang.String"), + CLS.FileOutputStream.$init.overload("java.lang.String", "boolean") + ], + write: [ + CLS.FileOutputStream.write.overload("[B"), + CLS.FileOutputStream.write.overload("int"), + CLS.FileOutputStream.write.overload("[B", "int", "int"), + ], + }; + + + + // ============= Hook implementation + + File.new[1].implementation = function(a0) { + prettyLog("[Java::File.new.1] New file : " + a0); + + var ret = File.new[1].call(this, a0); + var f = Java.cast(this, CLS.File); + TraceFile["f" + this.hashCode()] = a0; + + + return ret; + } + File.new[2].implementation = function(a0, a1) { + prettyLog("[Java::File.read.2] New file : " + a0 + "/" + a1); + + var ret = File.new[2].call(this, a0, a1);; + var f = Java.cast(this, CLS.File); + TraceFile["f" + this.hashCode()] = a0 + "/" + a1; + + return ret; + } + + + FileInputStream.new[0].implementation = function(a0) { + var file = Java.cast(a0, CLS.File); + var fname = TraceFile["f" + file.hashCode()]; + + if (fname == null) { + var p = file.getAbsolutePath(); + if (p !== null) + fname = TraceFile["f" + file.hashCode()] = p; + } + if (fname == null) + fname = "[unknow]" + + prettyLog("[Java::FileInputStream.new.0] New input stream from file (" + fname + "): "); + + var fis = FileInputStream.new[0].call(this, a0) + var f = Java.cast(this, CLS.FileInputStream); + + TraceFS["fd" + this.hashCode()] = fname; + + var fd = Java.cast(this.getFD(), CLS.FileDescriptor); + + TraceFD["fd" + fd.hashCode()] = fname; + + return fis; + } + + + + FileInputStream.read[1].implementation = function(a0) { + var fname = TraceFS["fd" + this.hashCode()]; + var fd = null; + if (fname == null) { + fd = Java.cast(this.getFD(), CLS.FileDescriptor); + fname = TraceFD["fd" + fd.hashCode()] + } + if (fname == null) + fname = "[unknow]"; + + var b = Java.array('byte', a0); + + prettyLog("[Java::FileInputStream.read.1] Read from file,offset (" + fname + "," + a0 + "):\n" + + prettyPrint(fname, b)); + + return FileInputStream.read[1].call(this, a0); + } + FileInputStream.read[2].implementation = function(a0, a1, a2) { + var fname = TraceFS["fd" + this.hashCode()]; + var fd = null; + if (fname == null) { + fd = Java.cast(this.getFD(), CLS.FileDescriptor); + fname = TraceFD["fd" + fd.hashCode()] + } + if (fname == null) + fname = "[unknow]"; + + var b = Java.array('byte', a0); + + prettyLog("[Java::FileInputStream.read.2] Read from file,offset,len (" + fname + "," + a1 + "," + a2 + ")\n" + + prettyPrint(fname, b)); + + return FileInputStream.read[2].call(this, a0, a1, a2); + } + + + + // =============== File Output Stream ============ + + + + FileOuputStream.new[0].implementation = function(a0) { + var file = Java.cast(a0, CLS.File); + var fname = TraceFile["f" + file.hashCode()]; + + if (fname == null) + fname = "[unknow]"; + + + prettyLog("[Java::FileOuputStream.new.0] New output stream to file (" + fname + "): "); + + var fis = FileOuputStream.new[0].call(this, a0); + + TraceFS["fd" + this.hashCode()] = fname; + + var fd = Java.cast(this.getFD(), CLS.FileDescriptor); + TraceFD["fd" + fd.hashCode()] = fname; + + return fis; + } + + FileOuputStream.new[1].implementation = function(a0) { + var file = Java.cast(a0, CLS.File); + var fname = TraceFile["f" + file.hashCode()]; + + if (fname == null) + fname = "[unknow]"; + + + prettyLog("[Java::FileOuputStream.new.1] New output stream to file (" + fname + "): \n"); + + var fis = FileOuputStream.new[1].call(this, a0); + + TraceFS["fd" + this.hashCode()] = fname; + + var fd = Java.cast(this.getFD(), CLS.FileDescriptor); + + TraceFD["fd" + fd.hashCode()] = fname; + + return fis; + } + + FileOuputStream.new[2].implementation = function(a0) { + var fd = Java.cast(a0, CLS.FileDescriptor); + var fname = TraceFD["fd" + fd.hashCode()]; + + if (fname == null) + fname = "[unknow]"; + + + prettyLog("[Java::FileOuputStream.new.2] New output stream to FileDescriptor (" + fname + "): \n"); + var fis = FileOuputStream.new[1].call(this, a0) + + TraceFS["fd" + this.hashCode()] = fname; + + return fis; + } + FileOuputStream.new[3].implementation = function(a0) { + prettyLog("[Java::FileOuputStream.new.3] New output stream to file (str=" + a0 + "): \n"); + + var fis = FileOuputStream.new[1].call(this, a0) + + TraceFS["fd" + this.hashCode()] = a0; + var fd = Java.cast(this.getFD(), CLS.FileDescriptor); + TraceFD["fd" + fd.hashCode()] = a0; + + return fis; + } + FileOuputStream.new[4].implementation = function(a0) { + prettyLog("[Java::FileOuputStream.new.4] New output stream to file (str=" + a0 + ",bool): \n"); + + var fis = FileOuputStream.new[1].call(this, a0) + TraceFS["fd" + this.hashCode()] = a0; + var fd = Java.cast(this.getFD(), CLS.FileDescriptor); + TraceFD["fd" + fd.hashCode()] = a0; + + return fis; + } + + + + FileOuputStream.write[0].implementation = function(a0) { + var fname = TraceFS["fd" + this.hashCode()]; + var fd = null; + + if (fname == null) { + fd = Java.cast(this.getFD(), CLS.FileDescriptor); + fname = TraceFD["fd" + fd.hashCode()] + } + if (fname == null) + fname = "[unknow]"; + + prettyLog("[Java::FileOuputStream.write.0] Write byte array (" + fname + "):\n" + + prettyPrint(fname, a0)); + + return FileOuputStream.write[0].call(this, a0); + } + FileOuputStream.write[1].implementation = function(a0) { + + var fname = TraceFS["fd" + this.hashCode()]; + var fd = null; + if (fname == null) { + fd = Java.cast(this.getFD(), CLS.FileDescriptor); + fname = TraceFD["fd" + fd.hashCode()] + } + if (fname == null) + fname = "[unknow]"; + + prettyLog("[Java::FileOuputStream.write.1] Write int (" + fname + "): " + a0); + + + return FileOuputStream.write[1].call(this, a0); + } + FileOuputStream.write[2].implementation = function(a0, a1, a2) { + + var fname = TraceFS["fd" + this.hashCode()]; + var fd = null; + if (fname == null) { + fd = Java.cast(this.getFD(), CLS.FileDescriptor); + fname = TraceFD["fd" + fd.hashCode()] + if (fname == null) + fname = "[unknow], fd=" + this.hashCode(); + } + + prettyLog("[Java::FileOuputStream.write.2] Write " + a2 + " bytes from " + a1 + " (" + fname + "):\n" + + prettyPrint(fname, a0)); + + return FileOuputStream.write[2].call(this, a0, a1, a2); + } + + // native hooks + Interceptor.attach( + Module.findExportByName("libc.so", "read"), { + // fd, buff, len + onEnter: function(args) { + if (CONFIG.printLibc === true) { + var bfr = args[1], + sz = args[2].toInt32(); + var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]"; + + prettyLog("[Libc::read] Read FD (" + path + "," + bfr + "," + sz + ")\n" + + rawPrint(path, Memory.readByteArray(bfr, sz))); + } + }, + onLeave: function(ret) { + + } + } + ); + + Interceptor.attach( + Module.findExportByName("libc.so", "open"), { + // path, flags, mode + onEnter: function(args) { + this.path = Memory.readCString(args[0]); + }, + onLeave: function(ret) { + TraceSysFD["fd-" + ret.toInt32()] = this.path; + if (CONFIG.printLibc === true) + prettyLog("[Libc::open] Open file '" + this.path + "' (fd: " + ret.toInt32() + ")"); + } + } + ); + + + Interceptor.attach( + Module.findExportByName("libc.so", "write"), { + // fd, buff, count + onEnter: function(args) { + if (CONFIG.printLibc === true) { + var bfr = args[1], + sz = args[2].toInt32(); + var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]"; + + prettyLog("[Libc::write] Write FD (" + path + "," + bfr + "," + sz + ")\n" + + rawPrint(path, Memory.readByteArray(bfr, sz))); + } + }, + onLeave: function(ret) { + + } + } + ); + + + + // helper functions + function prettyLog(str) { + console.log("---------------------------\n" + str); + if (CONFIG.printStackTrace === true) { + printStackTrace(); + } + } + + function prettyPrint(path, buffer) { + if (CONFIG.printEnable === false) return ""; + + if (contains(path, CONFIG.dump_ascii_If_Path_contains)) { + return b2s(buffer); + } else if (!contains(path, CONFIG.dump_hex_If_Path_NOT_contains)) { + return hexdump(b2s(buffer)); + } + return "[dump skipped by config]"; + } + + function rawPrint(path, buffer) { + if (CONFIG.printEnable === false) return ""; + + if (!contains(path, CONFIG.dump_raw_If_Path_NOT_contains)) { + return hexdump(buffer); + } + return "[dump skipped by config]"; + } + + function contains(path, patterns) { + for (var i = 0; i < patterns.length; i++) + if (path.indexOf(patterns[i]) > -1) return true; + return false; + } + + function printStackTrace() { + var th = Java.cast(CLS.Thread.currentThread(), CLS.Thread); + var stack = th.getStackTrace(), + e = null; + + for (var i = 0; i < stack.length; i++) { + console.log("\t" + stack[i].getClassName() + "." + stack[i].getMethodName() + "(" + stack[i].getFileName() + ")"); + } + } + + function isZero(block) { + var m = /^[0\s]+$/.exec(block); + return m != null && m.length > 0 && (m[0] == block); + } + + function hexdump(buffer, blockSize) { + blockSize = blockSize || 16; + var lines = []; + var hex = "0123456789ABCDEF"; + var prevZero = false, + ctrZero = 0; + for (var b = 0; b < buffer.length; b += blockSize) { + var block = buffer.slice(b, Math.min(b + blockSize, buffer.length)); + var addr = ("0000" + b.toString(16)).slice(-4); + var codes = block.split('').map(function(ch) { + var code = ch.charCodeAt(0); + return " " + hex[(0xF0 & code) >> 4] + hex[0x0F & code]; + }).join(""); + codes += " ".repeat(blockSize - block.length); + var chars = block.replace(/[\\x00-\\x1F\\x20\n]/g, '.'); + chars += " ".repeat(blockSize - block.length); + if (isZero(codes)) { + ctrZero += blockSize; + prevZero = true; + } else { + if (prevZero) { + lines.push("\t [" + ctrZero + "] bytes of zeroes"); + } + lines.push(addr + " " + codes + " " + chars); + prevZero = false; + ctrZero = 0; + } + } + if (prevZero) { + lines.push("\t [" + ctrZero + "] bytes of zeroes"); + } + return lines.join("\\n"); + } + + function b2s(array) { + var result = ""; + for (var i = 0; i < array.length; i++) { + result += String.fromCharCode(modulus(array[i], 256)); + } + return result; + } + + function modulus(x, n) { + return ((x % n) + n) % n; + } + +}); + diff --git a/frida/frida_scripts/frida_certpin_pass.js b/frida/frida_scripts/frida_certpin_pass.js new file mode 100644 index 0000000..bd0c729 --- /dev/null +++ b/frida/frida_scripts/frida_certpin_pass.js @@ -0,0 +1,66 @@ +/* + Android SSL Re-pinning frida script v0.2 030417-pier + + $ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt + $ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pause + + https://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/ + + UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 ! +*/ + +setTimeout(function(){ + Java.perform(function (){ + console.log(""); + console.log("[.] Cert Pinning Bypass/Re-Pinning"); + + var CertificateFactory = Java.use("java.security.cert.CertificateFactory"); + var FileInputStream = Java.use("java.io.FileInputStream"); + var BufferedInputStream = Java.use("java.io.BufferedInputStream"); + var X509Certificate = Java.use("java.security.cert.X509Certificate"); + var KeyStore = Java.use("java.security.KeyStore"); + var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory"); + var SSLContext = Java.use("javax.net.ssl.SSLContext"); + + // Load CAs from an InputStream + console.log("[+] Loading our CA...") + var cf = CertificateFactory.getInstance("X.509"); + + try { + var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt"); + } + catch(err) { + console.log("[o] " + err); + } + + var bufferedInputStream = BufferedInputStream.$new(fileInputStream); + var ca = cf.generateCertificate(bufferedInputStream); + bufferedInputStream.close(); + + var certInfo = Java.cast(ca, X509Certificate); + console.log("[o] Our CA Info: " + certInfo.getSubjectDN()); + + // Create a KeyStore containing our trusted CAs + console.log("[+] Creating a KeyStore for our CA..."); + var keyStoreType = KeyStore.getDefaultType(); + var keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore..."); + var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + var tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + console.log("[+] Our TrustManager is ready..."); + + console.log("[+] Hijacking SSLContext methods now...") + console.log("[-] Waiting for the app to invoke SSLContext.init()...") + + SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) { + console.log("[o] App invoked javax.net.ssl.SSLContext.init..."); + SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c); + console.log("[+] SSLContext initialized with our custom TrustManager!"); + } + }); +},0); diff --git a/frida/frida_scripts/hook-finder.js b/frida/frida_scripts/hook-finder.js new file mode 100644 index 0000000..1f6d376 --- /dev/null +++ b/frida/frida_scripts/hook-finder.js @@ -0,0 +1,251 @@ +// Script to gather the shared library from disk and also +// from memory utilizing Frida. After reading the file from +// disk, it will then compare some sections of the file in +// order to hunt and identify potentially modified and hooked +// functions. +// +// Re-written over the ages for usage while +// unpacking Android applications by +// Tim 'diff' Strazzere, +// Based off older code and concepts from lich4/lichao890427 +// +// Corresponding blog https://corellium.com/blog/android-frida-finding-hooks + +// Helper function for creating a native function for usage +function getNativeFunction(name, ret, args) { + var mod = Module.findExportByName(null, name); + if (mod === null) { + return null; + } + + var func = new NativeFunction(mod, ret, args); + if (typeof func === 'undefined') { + return null; + } + + return func; +} + +var open_ptr = getNativeFunction('open', 'int', ['pointer', 'int', 'int']); +var read_ptr = getNativeFunction('read', 'int', ['int', 'pointer', 'int']); +var close_ptr = getNativeFunction('close', 'int', ['int']); +var lseek_ptr = getNativeFunction('lseek', 'int', ['int', 'int', 'int']); + +function getElfData(module) { + console.log('Processing ', module.path); + if (module.sections) { + return true; + } + + var fd = open_ptr(Memory.allocUtf8String(module.path), 0 /* O_RDONLY */, 0); + if (fd == -1) { + return false; + } + + // Get elf header + var header = Memory.alloc(64); + lseek_ptr(fd, 0, 0 /* SEEK_SET */); + read_ptr(fd, header, 64); + + // Allow for both 32bit and 64bit binaries + var is32 = Memory.readU8(header.add(4)) === 1; + module.is32 = is32; + + // Parse section headers + var sectionHeaderOffset = is32 ? Memory.readU32(header.add(32)) : Memory.readU64(header.add(40)).toNumber(); // For some reason this is read as a string + var sectionHeaderSize = is32 ? Memory.readU16(header.add(46)) : Memory.readU16(header.add(58)); + var sectionHeaderCount = is32 ? Memory.readU16(header.add(48)) : Memory.readU16(header.add(60)); + var sectionHeaderStringTableIndex = is32 ? Memory.readU16(header.add(50)) : Memory.readU16(header.add(62)); + + var sectionHeaders = Memory.alloc(sectionHeaderSize * sectionHeaderCount); + + lseek_ptr(fd, sectionHeaderOffset, 0 /* SEEK_SET */); + read_ptr(fd, sectionHeaders, sectionHeaderSize * sectionHeaderCount); + + var stringTableOffset = is32 ? Memory.readU32(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 16)) : Memory.readU64(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 24)).toNumber(); + var stringTableSize = is32 ? Memory.readU32(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 20)) : Memory.readU64(sectionHeaders.add(sectionHeaderSize * sectionHeaderStringTableIndex + 32)).toNumber(); + + var stringTable = Memory.alloc(stringTableSize); + lseek_ptr(fd, stringTableOffset, 0 /* SEEK_SET */); + read_ptr(fd, stringTable, stringTableSize); + var sections = []; + + var dynsym = undefined; + var dynstr = undefined; + var relplt = undefined; + var reldyn = undefined; + + for (var i = 0; i < sectionHeaderCount; i++) { + var sectionName = Memory.readUtf8String(stringTable.add(Memory.readU32(sectionHeaders.add(i * sectionHeaderSize)))); + var sectionAddress = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 12)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 16)).toNumber(); + var sectionOffset = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 16)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 24)).toNumber(); + var sectionSize = is32 ? Memory.readU32(sectionHeaders.add(i * sectionHeaderSize + 20)) : Memory.readU64(sectionHeaders.add(i * sectionHeaderSize + 32)).toNumber(); + + if (['.text', '.rodata', '.got', '.got.plt'].includes(sectionName)) { + var section = {}; + section.name = sectionName; + section.memoryOffset = sectionAddress; + section.fileOffset = sectionOffset; + section.size = sectionSize; + if (sectionSize > 0) { + section.data = Memory.alloc(sectionSize); + lseek_ptr(fd, sectionOffset, 0 /* SEEK_SET */); + read_ptr(fd, section.data, sectionSize); + } else { + section.data = undefined; + } + sections.push(section); + } else if (['.dynsym', '.dynstr', '.rel.dyn', '.rel.plt'].includes(sectionName)) { + var section = {}; + section.name = sectionName; + section.memoryOffset = sectionAddress; + section.fileOffset = sectionOffset; + section.size = sectionSize; + if (sectionSize > 0) { + section.data = Memory.alloc(sectionSize); + lseek_ptr(fd, sectionOffset, 0 /* SEEK_SET */); + read_ptr(fd, section.data, sectionSize); + } else { + console.log('No data section for', section.name); + section.data = undefined; + } + + if (section.name === '.dynsym') { + dynsym = section; + } + if (section.name === '.dynstr') { + dynstr = section; + } + if (section.name === '.rel.dyn') { + reldyn = section; + } + if (section.name === '.rel.plt') { + relplt = section; + } + sections.push(section); + } + } + + if (!!dynsym && !!dynstr) { + var symbols = []; + var stringTable = module.base.add(dynstr.memoryOffset); + var structSize = is32 ? 16 : 24; + for (var i = 0; i < dynsym.size / structSize; i++) { + var symbolOffset = Memory.readU32(module.base.add(dynsym.memoryOffset).add(structSize * i)); + symbols.push(Memory.readUtf8String(stringTable.add(symbolOffset))); + } + + module.symbols = symbols; + } + + var relmap = new Map(); + if (!!reldyn) { + for (var i = 0; i < reldyn.size / 8; i++) { + if ((Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8)) != 0) && + (Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8).add(4)) >> 8 != 0)) { + relmap[Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8))] = Memory.readU32(module.base.add(reldyn.memoryOffset).add(i * 8).add(4)) >> 8; + } + } + } + + if (!!relplt) { + for (var i = 0; i < relplt.size / 8; i++) { + if ((Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8)) != 0) && + (Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8).add(4)) >> 8 != 0)) { + relmap[Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8))] = Memory.readU32(module.base.add(relplt.memoryOffset).add(i * 8).add(4)) >> 8; + } + } + } + module.relmap = relmap; + + module.sections = sections; + return true; +} + +function findHooks(module) { + if (module.sections === undefined) { + if (!getElfData(module)) { + return undefined; + } + } + + module.sections.forEach((section) => { + if (section.size === 0) { + return; + } + + // It's important to cast the ArrayBuffer returned by `readByteArray` cannot be referenced incrementally + var file = new Uint8Array(Memory.readByteArray(section.data, section.size)); + var memory = new Uint8Array(Memory.readByteArray(module.base.add(section.memoryOffset), section.size)); + for (var i = 0; i < section.size;) { + if (['.rodata', '.text'].includes(section.name)) { + if (file[i] != memory[i]) { + console.log('*** Potential variance found at ', DebugSymbol.fromAddress(module.base.add(section.memoryOffset).add(i))); + i += 4; + } + i++ + } else if (['.got'].includes(section.name)) { + break; + // It shouldn't be as the got table isn't initialized until execution + if (file[i] != memory[i]) { + // todo compare the symbol to string against what it resolves too + } + i += module.is32 ? 4 : 8; + } else { + // Unscanned sections, to be added as needed + break; + } + } + }); +} + +// Quick and simple way to get the package name, assumes that the script +// was injected into an APK otherwise it won't work. +function getPackageName() { + var fd = open_ptr(Memory.allocUtf8String('/proc/self/cmdline'), 0 /* O_RDONLY */, 0); + if (fd == -1) { + return 'null'; + } + + var buffer = Memory.alloc(32); + read_ptr(fd, buffer, 32); + close_ptr(fd); + + return Memory.readUtf8String(buffer); +} + +// Adjust this as needed, often I don't need to scan anything outside of the +// included shared libraries and a few which are almost always in an apex folder. +// This logic will need to be changed if you're using a pre-apex version of Android +// to ensure it picked up the proper libraries for hunting +// +// While it doesn't hurt to scan everything, it's almost never needed and will just slow +// down the process at a linear scale. +// +// If you already know what you're hunting for, feel free to just return or look for +// libart, libdvm, etc, etc +function getRelevantModules() { + var modules = []; + var packagename = getPackageName(); + + Process.enumerateModules().forEach((module) => { + if (module.path.includes(packagename)) { + modules.push(module); + console.log('Adding ', module.path); + } else if (module.path.includes('/apex')) { + modules.push(module); + console.log('Adding ', module.path); + } else { + console.log('Skipping ', module.path); + } + }) + + return modules; +} + +var modules = getRelevantModules(); + +modules.forEach((module) => { + getElfData(module); + findHooks(module); +}); diff --git a/frida/frida_scripts/http-connection.js b/frida/frida_scripts/http-connection.js new file mode 100644 index 0000000..d72ea63 --- /dev/null +++ b/frida/frida_scripts/http-connection.js @@ -0,0 +1,148 @@ +setImmediate(function() { + Java.perform(function() { + + var url = Java.use("java.net.URL"); + + url.$init.overload('java.lang.String').implementation = function (var0) { + console.log("[*] Created new URL with value: " + var0 +"\n"); + return this.$init(var0); + }; + + url.openConnection.overload().implementation = function () { + console.log("[*] Created new URL connection\n"); + return this.openConnection(); + }; + + url.openConnection.overload('java.net.Proxy').implementation = function (var0) { + console.log("[*] Created new URL connection with proxy value: " + var0 +"\n"); + return this.openConnection(var0); + }; + + + var URLConnection = Java.use("java.net.URLConnection"); + + URLConnection.connect.implementation = function () { + console.log("[*] Connect called.\n"); + this.connect(); + }; + + URLConnection.getContent.overload().implementation = function () { + var content = this.getContent(); + console.log("[*] Get content called. Content: " + content + "\n"); + return content; + }; + + URLConnection.getContentType.implementation = function () { + var contentType = this.getContentType(); + console.log("[*] Get content type called. Content type: " + contentType + "\n"); + return contentType; + }; + + URLConnection.getContentLength.implementation = function () { + var contentLength = this.getContentLength(); + console.log("[*] Get content length called. Content length: " + contentLength + "\n"); + return contentLength; + }; + + URLConnection.getContentLengthLong.implementation = function () { + var contentLengthLong = this.getContentLengthLong(); + console.log("[*] Get content length long called. Content length long: " + contentLengthLong + "\n"); + return contentLengthLong; + }; + + URLConnection.getContentEncoding.implementation = function () { + var contentEncoding = this.getContentEncoding(); + console.log("[*] Get content encoding called. Content encoding: " + contentEncoding + "\n"); + return contentEncoding; + }; + + URLConnection.getDate.implementation = function () { + var contentDate = this.getDate(); + console.log("[*] Get date called. Date: " + contentDate + "\n"); + return contentDate; + }; + + URLConnection.getExpiration.implementation = function () { + var contentExpiration = this.getExpiration(); + console.log("[*] Get expiration called. Expiration: " + contentExpiration + "\n"); + return contentExpiration; + }; + + URLConnection.getLastModified.implementation = function () { + var lastModified = this.getLastModified(); + console.log("[*] Get last modified called. Value: " + lastModified + "\n"); + return lastModified; + }; + + URLConnection.getInputStream.implementation = function () { + console.log("[*] Get input stream called.\n"); + return this.getInputStream; + }; + + URLConnection.setDoOutput.overload('boolean').implementation = function (var0) { + console.log("[*] URLConnection.setDoOutput called with value: " + var0 + ".\n"); + this.setDoOutput(var0); + }; + + URLConnection.setDoInput.overload('boolean').implementation = function (var0) { + console.log("[*] URLConnection.setDoInput called with value: " + var0 + ".\n"); + this.setDoInput(var0); + }; + + var httpURLConnection = Java.use("com.android.okhttp.internal.huc.HttpURLConnectionImpl"); + + httpURLConnection.setRequestMethod.overload('java.lang.String').implementation = function (var0) { + console.log("[*] Set request method called: " + var0 + "\n"); + this.setRequestMethod(var0); + }; + + httpURLConnection.setRequestMethod.overload('java.lang.String').implementation = function (var0) { + console.log("[*] Set request method called: " + var0 + "\n"); + this.setRequestMethod(var0); + }; + + httpURLConnection.connect.implementation = function () { + console.log("[*] Connect called.\n"); + this.connect(); + }; + + httpURLConnection.disconnect.implementation = function () { + console.log("[*] Disconnect called.\n"); + this.disconnect(); + }; + + httpURLConnection.getResponseCode.implementation = function () { + var responseCode = this.getResponseCode(); + console.log("[*] Get response code called: " + responseCode + "\n"); + return responseCode; + }; + + var httpsURLConnection = Java.use("com.android.okhttp.internal.huc.HttpsURLConnectionImpl"); + + httpsURLConnection.setRequestMethod.overload('java.lang.String').implementation = function (var0) { + console.log("[*] Set request method called: " + var0 + "\n"); + this.setRequestMethod(var0); + }; + + httpsURLConnection.connect.implementation = function () { + console.log("[*] Connect called.\n"); + this.connect(); + }; + + httpsURLConnection.disconnect.implementation = function () { + console.log("[*] Disconnect called.\n"); + this.disconnect(); + }; + + httpsURLConnection.getResponseCode.implementation = function () { + var responseCode = this.getResponseCode(); + console.log("[*] Get response code called: " + responseCode + "\n"); + return responseCode; + }; + + httpsURLConnection.setRequestProperty.overload('java.lang.String', 'java.lang.String').implementation = function (var0, var1) { + console.log("[*] URLConnection.setRequestProperty called with key: " + var0 + " and value: " + var1 + ".\n"); + this.setRequestProperty(var0, var1); + }; + }); +}); diff --git a/frida/frida_scripts/multiple_unpinning.js b/frida/frida_scripts/multiple_unpinning.js new file mode 100644 index 0000000..9c29aab --- /dev/null +++ b/frida/frida_scripts/multiple_unpinning.js @@ -0,0 +1,731 @@ +/* Android ssl certificate pinning bypass script for various methods + by Maurizio Siddu + + Run with: + frida -U -f [APP_ID] -l frida_multiple_unpinning.js --no-pause +*/ + +setTimeout(function() { + Java.perform(function() { + console.log(''); + console.log('======'); + console.log('[#] Android Bypass for various Certificate Pinning methods [#]'); + console.log('======'); + + + var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); + var SSLContext = Java.use('javax.net.ssl.SSLContext'); + + // TrustManager (Android < 7) // + //////////////////////////////// + var 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() + var TrustManagers = [TrustManager.$new()]; + // Get a handle on the init() on the SSLContext class + var SSLContext_init = SSLContext.init.overload( + '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom'); + try { + // Override the init method, specifying the custom TrustManager + SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) { + console.log('[+] Bypassing Trustmanager (Android < 7) pinner'); + SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); + }; + } catch (err) { + console.log('[-] TrustManager (Android < 7) pinner not found'); + //console.log(err); + } + + + + + // OkHTTPv3 (quadruple bypass) // + ///////////////////////////////// + try { + // Bypass OkHTTPv3 {1} + var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) { + console.log('[+] Bypassing OkHTTPv3 {1}: ' + a); + return; + }; + } catch (err) { + console.log('[-] OkHTTPv3 {1} pinner not found'); + //console.log(err); + } + try { + // Bypass OkHTTPv3 {2} + // This method of CertificatePinner.check is deprecated but could be found in some old Android apps + var okhttp3_Activity_2 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_2.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function(a, b) { + console.log('[+] Bypassing OkHTTPv3 {2}: ' + a); + return; + }; + } catch (err) { + console.log('[-] OkHTTPv3 {2} pinner not found'); + //console.log(err); + } + try { + // Bypass OkHTTPv3 {3} + var okhttp3_Activity_3 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_3.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(a, b) { + console.log('[+] Bypassing OkHTTPv3 {3}: ' + a); + return; + }; + } catch(err) { + console.log('[-] OkHTTPv3 {3} pinner not found'); + //console.log(err); + } + try { + // Bypass OkHTTPv3 {4} + var okhttp3_Activity_4 = Java.use('okhttp3.CertificatePinner'); + //okhttp3_Activity_4['check$okhttp'].implementation = function(a, b) { + okhttp3_Activity_4.check$okhttp.overload('java.lang.String', 'kotlin.jvm.functions.Function0').implementation = function(a, b) { + console.log('[+] Bypassing OkHTTPv3 {4}: ' + a); + return; + }; + } catch(err) { + console.log('[-] OkHTTPv3 {4} pinner not found'); + //console.log(err); + } + + + + + // Trustkit (triple bypass) // + ////////////////////////////// + try { + // Bypass Trustkit {1} + var trustkit_Activity_1 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'); + trustkit_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b) { + console.log('[+] Bypassing Trustkit {1}: ' + a); + return true; + }; + } catch (err) { + console.log('[-] Trustkit {1} pinner not found'); + //console.log(err); + } + try { + // Bypass Trustkit {2} + var trustkit_Activity_2 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'); + trustkit_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(a, b) { + console.log('[+] Bypassing Trustkit {2}: ' + a); + return true; + }; + } catch (err) { + console.log('[-] Trustkit {2} pinner not found'); + //console.log(err); + } + try { + // Bypass Trustkit {3} + var trustkit_PinningTrustManager = Java.use('com.datatheorem.android.trustkit.pinning.PinningTrustManager'); + trustkit_PinningTrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function(chain, authType) { + console.log('[+] Bypassing Trustkit {3}'); + //return; + }; + } catch (err) { + console.log('[-] Trustkit {3} pinner not found'); + //console.log(err); + } + + + + + // TrustManagerImpl (Android > 7) // + //////////////////////////////////// + try { + // Bypass TrustManagerImpl (Android > 7) {1} + var array_list = Java.use("java.util.ArrayList"); + var TrustManagerImpl_Activity_1 = Java.use('com.android.org.conscrypt.TrustManagerImpl'); + TrustManagerImpl_Activity_1.checkTrustedRecursive.implementation = function(certs, ocspData, tlsSctData, host, clientAuth, untrustedChain, trustAnchorChain, used) { + console.log('[+] Bypassing TrustManagerImpl (Android > 7) checkTrustedRecursive check: '+ host); + return array_list.$new(); + }; + } catch (err) { + console.log('[-] TrustManagerImpl (Android > 7) checkTrustedRecursive check not found'); + //console.log(err); + } + try { + // Bypass TrustManagerImpl (Android > 7) {2} (probably no more necessary) + var TrustManagerImpl_Activity_2 = Java.use('com.android.org.conscrypt.TrustManagerImpl'); + TrustManagerImpl_Activity_2.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { + console.log('[+] Bypassing TrustManagerImpl (Android > 7) verifyChain check: ' + host); + return untrustedChain; + }; + } catch (err) { + console.log('[-] TrustManagerImpl (Android > 7) verifyChain check not found'); + //console.log(err); + } + + + + + + // Appcelerator Titanium PinningTrustManager // + /////////////////////////////////////////////// + try { + var appcelerator_PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager'); + appcelerator_PinningTrustManager.checkServerTrusted.implementation = function(chain, authType) { + console.log('[+] Bypassing Appcelerator PinningTrustManager'); + return; + }; + } catch (err) { + console.log('[-] Appcelerator PinningTrustManager pinner not found'); + //console.log(err); + } + + + + + // Fabric PinningTrustManager // + //////////////////////////////// + try { + var fabric_PinningTrustManager = Java.use('io.fabric.sdk.android.services.network.PinningTrustManager'); + fabric_PinningTrustManager.checkServerTrusted.implementation = function(chain, authType) { + console.log('[+] Bypassing Fabric PinningTrustManager'); + return; + }; + } catch (err) { + console.log('[-] Fabric PinningTrustManager pinner not found'); + //console.log(err); + } + + + + + // OpenSSLSocketImpl Conscrypt (double bypass) // + ///////////////////////////////////////////////// + try { + var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl'); + OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, JavaObject, authMethod) { + console.log('[+] Bypassing OpenSSLSocketImpl Conscrypt {1}'); + }; + } catch (err) { + console.log('[-] OpenSSLSocketImpl Conscrypt {1} pinner not found'); + //console.log(err); + } + try { + var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl'); + OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certChain, authMethod) { + console.log('[+] Bypassing OpenSSLSocketImpl Conscrypt {2}'); + }; + } catch (err) { + console.log('[-] OpenSSLSocketImpl Conscrypt {2} pinner not found'); + //console.log(err); + } + + + + + // OpenSSLEngineSocketImpl Conscrypt // + /////////////////////////////////////// + try { + var OpenSSLEngineSocketImpl_Activity = Java.use('com.android.org.conscrypt.OpenSSLEngineSocketImpl'); + OpenSSLEngineSocketImpl_Activity.verifyCertificateChain.overload('[Ljava.lang.Long;', 'java.lang.String').implementation = function(a, b) { + console.log('[+] Bypassing OpenSSLEngineSocketImpl Conscrypt: ' + b); + }; + } catch (err) { + console.log('[-] OpenSSLEngineSocketImpl Conscrypt pinner not found'); + //console.log(err); + } + + + + + // OpenSSLSocketImpl Apache Harmony // + ////////////////////////////////////// + try { + var OpenSSLSocketImpl_Harmony = Java.use('org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl'); + OpenSSLSocketImpl_Harmony.verifyCertificateChain.implementation = function(asn1DerEncodedCertificateChain, authMethod) { + console.log('[+] Bypassing OpenSSLSocketImpl Apache Harmony'); + }; + } catch (err) { + console.log('[-] OpenSSLSocketImpl Apache Harmony pinner not found'); + //console.log(err); + } + + + + + // PhoneGap sslCertificateChecker // + //////////////////////////////////// + try { + var phonegap_Activity = Java.use('nl.xservices.plugins.sslCertificateChecker'); + phonegap_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function(a, b, c) { + console.log('[+] Bypassing PhoneGap sslCertificateChecker: ' + a); + return true; + }; + } catch (err) { + console.log('[-] PhoneGap sslCertificateChecker pinner not found'); + //console.log(err); + } + + + + + // IBM MobileFirst pinTrustedCertificatePublicKey (double bypass) // + //////////////////////////////////////////////////////////////////// + try { + // Bypass IBM MobileFirst {1} + var WLClient_Activity_1 = Java.use('com.worklight.wlclient.api.WLClient'); + WLClient_Activity_1.getInstance().pinTrustedCertificatePublicKey.overload('java.lang.String').implementation = function(cert) { + console.log('[+] Bypassing IBM MobileFirst pinTrustedCertificatePublicKey {1}: ' + cert); + return; + }; + } catch (err) { + console.log('[-] IBM MobileFirst pinTrustedCertificatePublicKey {1} pinner not found'); + //console.log(err); + } + try { + // Bypass IBM MobileFirst {2} + var WLClient_Activity_2 = Java.use('com.worklight.wlclient.api.WLClient'); + WLClient_Activity_2.getInstance().pinTrustedCertificatePublicKey.overload('[Ljava.lang.String;').implementation = function(cert) { + console.log('[+] Bypassing IBM MobileFirst pinTrustedCertificatePublicKey {2}: ' + cert); + return; + }; + } catch (err) { + console.log('[-] IBM MobileFirst pinTrustedCertificatePublicKey {2} pinner not found'); + //console.log(err); + } + + + + + // IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass) // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + try { + // Bypass IBM WorkLight {1} + var worklight_Activity_1 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSocket').implementation = function(a, b) { + console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {1}: ' + a); + return; + }; + } catch (err) { + console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {1} pinner not found'); + //console.log(err); + } + try { + // Bypass IBM WorkLight {2} + var worklight_Activity_2 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(a, b) { + console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {2}: ' + a); + return; + }; + } catch (err) { + console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {2} pinner not found'); + //console.log(err); + } + try { + // Bypass IBM WorkLight {3} + var worklight_Activity_3 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_3.verify.overload('java.lang.String', '[Ljava.lang.String;', '[Ljava.lang.String;').implementation = function(a, b) { + console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {3}: ' + a); + return; + }; + } catch (err) { + console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {3} pinner not found'); + //console.log(err); + } + try { + // Bypass IBM WorkLight {4} + var worklight_Activity_4 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_4.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(a, b) { + console.log('[+] Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning {4}: ' + a); + return true; + }; + } catch (err) { + console.log('[-] IBM WorkLight HostNameVerifierWithCertificatePinning {4} pinner not found'); + //console.log(err); + } + + + + + // Conscrypt CertPinManager // + ////////////////////////////// + try { + var conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager'); + conscrypt_CertPinManager_Activity.checkChainPinning.overload('java.lang.String', 'java.util.List').implementation = function(a, b) { + console.log('[+] Bypassing Conscrypt CertPinManager: ' + a); + //return; + return true; + }; + } catch (err) { + console.log('[-] Conscrypt CertPinManager pinner not found'); + //console.log(err); + } + + + + + // Conscrypt CertPinManager (Legacy) // + /////////////////////////////////////// + try { + var legacy_conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager'); + legacy_conscrypt_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function(a, b) { + console.log('[+] Bypassing Conscrypt CertPinManager (Legacy): ' + a); + return true; + }; + } catch (err) { + console.log('[-] Conscrypt CertPinManager (Legacy) pinner not found'); + //console.log(err); + } + + + + + // CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager // + /////////////////////////////////////////////////////////////////////////////////// + try { + var cwac_CertPinManager_Activity = Java.use('com.commonsware.cwac.netsecurity.conscrypt.CertPinManager'); + cwac_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function(a, b) { + console.log('[+] Bypassing CWAC-Netsecurity CertPinManager: ' + a); + return true; + }; + } catch (err) { + console.log('[-] CWAC-Netsecurity CertPinManager pinner not found'); + //console.log(err); + } + + + + + // Worklight Androidgap WLCertificatePinningPlugin // + ///////////////////////////////////////////////////// + try { + var androidgap_WLCertificatePinningPlugin_Activity = Java.use('com.worklight.androidgap.plugin.WLCertificatePinningPlugin'); + androidgap_WLCertificatePinningPlugin_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function(a, b, c) { + console.log('[+] Bypassing Worklight Androidgap WLCertificatePinningPlugin: ' + a); + return true; + }; + } catch (err) { + console.log('[-] Worklight Androidgap WLCertificatePinningPlugin pinner not found'); + //console.log(err); + } + + + + + // Netty FingerprintTrustManagerFactory // + ////////////////////////////////////////// + try { + var netty_FingerprintTrustManagerFactory = Java.use('io.netty.handler.ssl.util.FingerprintTrustManagerFactory'); + //NOTE: sometimes this below implementation could be useful + //var netty_FingerprintTrustManagerFactory = Java.use('org.jboss.netty.handler.ssl.util.FingerprintTrustManagerFactory'); + netty_FingerprintTrustManagerFactory.checkTrusted.implementation = function(type, chain) { + console.log('[+] Bypassing Netty FingerprintTrustManagerFactory'); + }; + } catch (err) { + console.log('[-] Netty FingerprintTrustManagerFactory pinner not found'); + //console.log(err); + } + + + + + // Squareup CertificatePinner [OkHTTP + stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException" + ); + // Retrieve the method raising the SSLPeerUnverifiedException + var callingFunctionStack = stackTrace[exceptionStackIndex + 1]; + var className = callingFunctionStack.getClassName(); + var methodName = callingFunctionStack.getMethodName(); + var callingClass = Java.use(className); + var callingMethod = callingClass[methodName]; + console.log('\x1b[36m[!] Attempting to bypass uncommon SSL Pinning method on: '+className+'.'+methodName+'\x1b[0m'); + // Skip it when already patched by Frida + if (callingMethod.implementation) { + return; + } + // Trying to patch the uncommon SSL Pinning method via implementation + var returnTypeName = callingMethod.returnType.type; + callingMethod.implementation = function() { + rudimentaryFix(returnTypeName); + }; + } catch (e) { + // Dynamic patching via implementation does not works, then trying via function overloading + //console.log('[!] The uncommon SSL Pinning method has more than one overload); + if (String(e).includes(".overload")) { + var splittedList = String(e).split(".overload"); + for (let i=2; i Unexpected SSL verification failure, adding dynamic patch...'); + + try { + const stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace(); + const exceptionStackIndex = stackTrace.findIndex(stack => + stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException" + ); + const callingFunctionStack = stackTrace[exceptionStackIndex + 1]; + + const className = callingFunctionStack.getClassName(); + const methodName = callingFunctionStack.getMethodName(); + + console.log(` Thrown by ${className}->${methodName}`); + + const callingClass = Java.use(className); + const callingMethod = callingClass[methodName]; + + if (callingMethod.implementation) return; // Already patched by Frida - skip it + + console.log(' Attempting to patch automatically...'); + const returnTypeName = callingMethod.returnType.type; + + callingMethod.implementation = function () { + console.log(` --> Bypassing ${className}->${methodName} (automatic exception patch)`); + + // This is not a perfect fix! Most unknown cases like this are really just + // checkCert(cert) methods though, so doing nothing is perfect, and if we + // do need an actual return value then this is probably the best we can do, + // and at least we're logging the method name so you can patch it manually: + + if (returnTypeName === 'void') { + return; + } else { + return null; + } + }; + + console.log(` [+] ${className}->${methodName} (automatic exception patch)`); + } catch (e) { + console.log(' [ ] Failed to automatically patch failure'); + } + + return this.$init(str); + }; + console.log('[+] SSLPeerUnverifiedException auto-patcher'); + } catch (err) { + console.log('[ ] SSLPeerUnverifiedException auto-patcher'); + } + + /// -- Specific targeted hooks: -- /// + + // HttpsURLConnection + try { + const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); + HttpsURLConnection.setDefaultHostnameVerifier.implementation = function (hostnameVerifier) { + console.log(' --> Bypassing HttpsURLConnection (setDefaultHostnameVerifier)'); + return; // Do nothing, i.e. don't change the hostname verifier + }; + console.log('[+] HttpsURLConnection (setDefaultHostnameVerifier)'); + } catch (err) { + console.log('[ ] HttpsURLConnection (setDefaultHostnameVerifier)'); + } + try { + const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); + HttpsURLConnection.setSSLSocketFactory.implementation = function (SSLSocketFactory) { + console.log(' --> Bypassing HttpsURLConnection (setSSLSocketFactory)'); + return; // Do nothing, i.e. don't change the SSL socket factory + }; + console.log('[+] HttpsURLConnection (setSSLSocketFactory)'); + } catch (err) { + console.log('[ ] HttpsURLConnection (setSSLSocketFactory)'); + } + try { + const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); + HttpsURLConnection.setHostnameVerifier.implementation = function (hostnameVerifier) { + console.log(' --> Bypassing HttpsURLConnection (setHostnameVerifier)'); + return; // Do nothing, i.e. don't change the hostname verifier + }; + console.log('[+] HttpsURLConnection (setHostnameVerifier)'); + } catch (err) { + console.log('[ ] HttpsURLConnection (setHostnameVerifier)'); + } + + // SSLContext + 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(' --> Bypassing Trustmanager (Android < 7) request'); + SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); + }; + console.log('[+] SSLContext'); + } catch (err) { + console.log('[ ] SSLContext'); + } + + // TrustManagerImpl (Android > 7) + try { + const array_list = Java.use("java.util.ArrayList"); + const TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); + + // This step is notably what defeats the most common case: network security config + TrustManagerImpl.checkTrustedRecursive.implementation = function(a1, a2, a3, a4, a5, a6) { + console.log(' --> Bypassing TrustManagerImpl checkTrusted '); + return array_list.$new(); + } + + TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { + console.log(' --> Bypassing TrustManagerImpl verifyChain: ' + host); + return untrustedChain; + }; + console.log('[+] TrustManagerImpl'); + } catch (err) { + console.log('[ ] TrustManagerImpl'); + } + + // OkHTTPv3 (quadruple bypass) + try { + // Bypass OkHTTPv3 {1} + const okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { + console.log(' --> Bypassing OkHTTPv3 (list): ' + a); + return; + }; + console.log('[+] OkHTTPv3 (list)'); + } catch (err) { + console.log('[ ] OkHTTPv3 (list)'); + } + try { + // Bypass OkHTTPv3 {2} + // This method of CertificatePinner.check could be found in some old Android app + const okhttp3_Activity_2 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_2.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function (a, b) { + console.log(' --> Bypassing OkHTTPv3 (cert): ' + a); + return; + }; + console.log('[+] OkHTTPv3 (cert)'); + } catch (err) { + console.log('[ ] OkHTTPv3 (cert)'); + } + try { + // Bypass OkHTTPv3 {3} + const okhttp3_Activity_3 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_3.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function (a, b) { + console.log(' --> Bypassing OkHTTPv3 (cert array): ' + a); + return; + }; + console.log('[+] OkHTTPv3 (cert array)'); + } catch (err) { + console.log('[ ] OkHTTPv3 (cert array)'); + } + try { + // Bypass OkHTTPv3 {4} + const okhttp3_Activity_4 = Java.use('okhttp3.CertificatePinner'); + okhttp3_Activity_4['check$okhttp'].implementation = function (a, b) { + console.log(' --> Bypassing OkHTTPv3 ($okhttp): ' + a); + return; + }; + console.log('[+] OkHTTPv3 ($okhttp)'); + } catch (err) { + console.log('[ ] OkHTTPv3 ($okhttp)'); + } + + // Trustkit (triple bypass) + try { + // Bypass Trustkit {1} + const trustkit_Activity_1 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'); + trustkit_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) { + console.log(' --> Bypassing Trustkit OkHostnameVerifier(SSLSession): ' + a); + return true; + }; + console.log('[+] Trustkit OkHostnameVerifier(SSLSession)'); + } catch (err) { + console.log('[ ] Trustkit OkHostnameVerifier(SSLSession)'); + } + try { + // Bypass Trustkit {2} + const trustkit_Activity_2 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'); + trustkit_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) { + console.log(' --> Bypassing Trustkit OkHostnameVerifier(cert): ' + a); + return true; + }; + console.log('[+] Trustkit OkHostnameVerifier(cert)'); + } catch (err) { + console.log('[ ] Trustkit OkHostnameVerifier(cert)'); + } + try { + // Bypass Trustkit {3} + const trustkit_PinningTrustManager = Java.use('com.datatheorem.android.trustkit.pinning.PinningTrustManager'); + trustkit_PinningTrustManager.checkServerTrusted.implementation = function () { + console.log(' --> Bypassing Trustkit PinningTrustManager'); + }; + console.log('[+] Trustkit PinningTrustManager'); + } catch (err) { + console.log('[ ] Trustkit PinningTrustManager'); + } + + // Appcelerator Titanium + try { + const appcelerator_PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager'); + appcelerator_PinningTrustManager.checkServerTrusted.implementation = function () { + console.log(' --> Bypassing Appcelerator PinningTrustManager'); + }; + console.log('[+] Appcelerator PinningTrustManager'); + } catch (err) { + console.log('[ ] Appcelerator PinningTrustManager'); + } + + // OpenSSLSocketImpl Conscrypt + try { + const OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl'); + OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, JavaObject, authMethod) { + console.log(' --> Bypassing OpenSSLSocketImpl Conscrypt'); + }; + console.log('[+] OpenSSLSocketImpl Conscrypt'); + } catch (err) { + console.log('[ ] OpenSSLSocketImpl Conscrypt'); + } + + // OpenSSLEngineSocketImpl Conscrypt + try { + const OpenSSLEngineSocketImpl_Activity = Java.use('com.android.org.conscrypt.OpenSSLEngineSocketImpl'); + OpenSSLEngineSocketImpl_Activity.verifyCertificateChain.overload('[Ljava.lang.Long;', 'java.lang.String').implementation = function (a, b) { + console.log(' --> Bypassing OpenSSLEngineSocketImpl Conscrypt: ' + b); + }; + console.log('[+] OpenSSLEngineSocketImpl Conscrypt'); + } catch (err) { + console.log('[ ] OpenSSLEngineSocketImpl Conscrypt'); + } + + // OpenSSLSocketImpl Apache Harmony + try { + const OpenSSLSocketImpl_Harmony = Java.use('org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl'); + OpenSSLSocketImpl_Harmony.verifyCertificateChain.implementation = function (asn1DerEncodedCertificateChain, authMethod) { + console.log(' --> Bypassing OpenSSLSocketImpl Apache Harmony'); + }; + console.log('[+] OpenSSLSocketImpl Apache Harmony'); + } catch (err) { + console.log('[ ] OpenSSLSocketImpl Apache Harmony'); + } + + // PhoneGap sslCertificateChecker (https://github.com/EddyVerbruggen/SSLCertificateChecker-PhoneGap-Plugin) + try { + const phonegap_Activity = Java.use('nl.xservices.plugins.sslCertificateChecker'); + phonegap_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (a, b, c) { + console.log(' --> Bypassing PhoneGap sslCertificateChecker: ' + a); + return true; + }; + console.log('[+] PhoneGap sslCertificateChecker'); + } catch (err) { + console.log('[ ] PhoneGap sslCertificateChecker'); + } + + // IBM MobileFirst pinTrustedCertificatePublicKey (double bypass) + try { + // Bypass IBM MobileFirst {1} + const WLClient_Activity_1 = Java.use('com.worklight.wlclient.api.WLClient'); + WLClient_Activity_1.getInstance().pinTrustedCertificatePublicKey.overload('java.lang.String').implementation = function (cert) { + console.log(' --> Bypassing IBM MobileFirst pinTrustedCertificatePublicKey (string): ' + cert); + return; + }; + console.log('[+] IBM MobileFirst pinTrustedCertificatePublicKey (string)'); + } catch (err) { + console.log('[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string)'); + } + try { + // Bypass IBM MobileFirst {2} + const WLClient_Activity_2 = Java.use('com.worklight.wlclient.api.WLClient'); + WLClient_Activity_2.getInstance().pinTrustedCertificatePublicKey.overload('[Ljava.lang.String;').implementation = function (cert) { + console.log(' --> Bypassing IBM MobileFirst pinTrustedCertificatePublicKey (string array): ' + cert); + return; + }; + console.log('[+] IBM MobileFirst pinTrustedCertificatePublicKey (string array)'); + } catch (err) { + console.log('[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string array)'); + } + + // IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass) + try { + // Bypass IBM WorkLight {1} + const worklight_Activity_1 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSocket').implementation = function (a, b) { + console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket): ' + a); + return; + }; + console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)'); + } catch (err) { + console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)'); + } + try { + // Bypass IBM WorkLight {2} + const worklight_Activity_2 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) { + console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (cert): ' + a); + return; + }; + console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)'); + } catch (err) { + console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)'); + } + try { + // Bypass IBM WorkLight {3} + const worklight_Activity_3 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_3.verify.overload('java.lang.String', '[Ljava.lang.String;', '[Ljava.lang.String;').implementation = function (a, b) { + console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (string string): ' + a); + return; + }; + console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)'); + } catch (err) { + console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)'); + } + try { + // Bypass IBM WorkLight {4} + const worklight_Activity_4 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); + worklight_Activity_4.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) { + console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession): ' + a); + return true; + }; + console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)'); + } catch (err) { + console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)'); + } + + // Conscrypt CertPinManager + try { + const conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager'); + conscrypt_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { + console.log(' --> Bypassing Conscrypt CertPinManager: ' + a); + return true; + }; + console.log('[+] Conscrypt CertPinManager'); + } catch (err) { + console.log('[ ] Conscrypt CertPinManager'); + } + + // CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager + try { + const cwac_CertPinManager_Activity = Java.use('com.commonsware.cwac.netsecurity.conscrypt.CertPinManager'); + cwac_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { + console.log(' --> Bypassing CWAC-Netsecurity CertPinManager: ' + a); + return true; + }; + console.log('[+] CWAC-Netsecurity CertPinManager'); + } catch (err) { + console.log('[ ] CWAC-Netsecurity CertPinManager'); + } + + // Worklight Androidgap WLCertificatePinningPlugin + try { + const androidgap_WLCertificatePinningPlugin_Activity = Java.use('com.worklight.androidgap.plugin.WLCertificatePinningPlugin'); + androidgap_WLCertificatePinningPlugin_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (a, b, c) { + console.log(' --> Bypassing Worklight Androidgap WLCertificatePinningPlugin: ' + a); + return true; + }; + console.log('[+] Worklight Androidgap WLCertificatePinningPlugin'); + } catch (err) { + console.log('[ ] Worklight Androidgap WLCertificatePinningPlugin'); + } + + // Netty FingerprintTrustManagerFactory + try { + const netty_FingerprintTrustManagerFactory = Java.use('io.netty.handler.ssl.util.FingerprintTrustManagerFactory'); + netty_FingerprintTrustManagerFactory.checkTrusted.implementation = function (type, chain) { + console.log(' --> Bypassing Netty FingerprintTrustManagerFactory'); + }; + console.log('[+] Netty FingerprintTrustManagerFactory'); + } catch (err) { + console.log('[ ] Netty FingerprintTrustManagerFactory'); + } + + // Squareup CertificatePinner [OkHTTP Bypassing Squareup CertificatePinner (cert): ' + a); + return; + }; + console.log('[+] Squareup CertificatePinner (cert)'); + } catch (err) { + console.log('[ ] Squareup CertificatePinner (cert)'); + } + try { + // Bypass Squareup CertificatePinner {2} + const Squareup_CertificatePinner_Activity_2 = Java.use('com.squareup.okhttp.CertificatePinner'); + Squareup_CertificatePinner_Activity_2.check.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { + console.log(' --> Bypassing Squareup CertificatePinner (list): ' + a); + return; + }; + console.log('[+] Squareup CertificatePinner (list)'); + } catch (err) { + console.log('[ ] Squareup CertificatePinner (list)'); + } + + // Squareup OkHostnameVerifier [OkHTTP v3] (double bypass) + try { + // Bypass Squareup OkHostnameVerifier {1} + const Squareup_OkHostnameVerifier_Activity_1 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier'); + Squareup_OkHostnameVerifier_Activity_1.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) { + console.log(' --> Bypassing Squareup OkHostnameVerifier (cert): ' + a); + return true; + }; + console.log('[+] Squareup OkHostnameVerifier (cert)'); + } catch (err) { + console.log('[ ] Squareup OkHostnameVerifier (cert)'); + } + try { + // Bypass Squareup OkHostnameVerifier {2} + const Squareup_OkHostnameVerifier_Activity_2 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier'); + Squareup_OkHostnameVerifier_Activity_2.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) { + console.log(' --> Bypassing Squareup OkHostnameVerifier (SSLSession): ' + a); + return true; + }; + console.log('[+] Squareup OkHostnameVerifier (SSLSession)'); + } catch (err) { + console.log('[ ] Squareup OkHostnameVerifier (SSLSession)'); + } + + // Android WebViewClient (double bypass) + try { + // Bypass WebViewClient {1} (deprecated from Android 6) + const AndroidWebViewClient_Activity_1 = Java.use('android.webkit.WebViewClient'); + AndroidWebViewClient_Activity_1.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function (obj1, obj2, obj3) { + console.log(' --> Bypassing Android WebViewClient (SslErrorHandler)'); + }; + console.log('[+] Android WebViewClient (SslErrorHandler)'); + } catch (err) { + console.log('[ ] Android WebViewClient (SslErrorHandler)'); + } + try { + // Bypass WebViewClient {2} + const AndroidWebViewClient_Activity_2 = Java.use('android.webkit.WebViewClient'); + AndroidWebViewClient_Activity_2.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function (obj1, obj2, obj3) { + console.log(' --> Bypassing Android WebViewClient (WebResourceError)'); + }; + console.log('[+] Android WebViewClient (WebResourceError)'); + } catch (err) { + console.log('[ ] Android WebViewClient (WebResourceError)'); + } + + // Apache Cordova WebViewClient + try { + const CordovaWebViewClient_Activity = Java.use('org.apache.cordova.CordovaWebViewClient'); + CordovaWebViewClient_Activity.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function (obj1, obj2, obj3) { + console.log(' --> Bypassing Apache Cordova WebViewClient'); + obj3.proceed(); + }; + } catch (err) { + console.log('[ ] Apache Cordova WebViewClient'); + } + + // Boye AbstractVerifier + try { + const boye_AbstractVerifier = Java.use('ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier'); + boye_AbstractVerifier.verify.implementation = function (host, ssl) { + console.log(' --> Bypassing Boye AbstractVerifier: ' + host); + }; + } catch (err) { + console.log('[ ] Boye AbstractVerifier'); + } + + // Appmattus + try { + const appmatus_Activity = Java.use('com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor'); + appmatus_Activity['intercept'].implementation = function (a) { + console.log(' --> Bypassing Appmattus (Transparency)'); + return a.proceed(a.request()); + }; + console.log('[+] Appmattus (Transparency)'); + } catch (err) { + console.log('[ ] Appmattus (Transparency)'); + } + + console.log("Unpinning setup completed"); + console.log("---"); + }); + +}, 0); + diff --git a/frida/frida_scripts/printStrings.js b/frida/frida_scripts/printStrings.js new file mode 100644 index 0000000..dcddfa0 --- /dev/null +++ b/frida/frida_scripts/printStrings.js @@ -0,0 +1,38 @@ +var objc_copyClassNamesForImage = new NativeFunction( + Module.findExportByName(null, 'objc_copyClassNamesForImage'), + 'pointer', + ['pointer', 'pointer'] +); +var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer']); +var classes = new Array(count); +var p = Memory.alloc(Process.pointerSize); + +Memory.writeUInt(p, 0); + +var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String(); +var pPath = Memory.allocUtf8String(path); +var pClasses = objc_copyClassNamesForImage(pPath, p); +var count = Memory.readUInt(p); +for (var i = 0; i < count; i++) { + var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize)); + classes[i] = Memory.readUtf8String(pClassName); +} + +free(pClasses); + +var tree = {}; +classes.forEach(function(name) { + var clazz = ObjC.classes[name]; + var chain = [name]; + while (clazz = clazz.$superClass) { + chain.unshift(clazz.$className); + } + + var node = tree; + chain.forEach(function(clazz) { + node[clazz] = node[clazz] || {}; + node = node[clazz]; + }); +}); + +send(tree); diff --git a/frida/frida_scripts/raptor_android_enum.js b/frida/frida_scripts/raptor_android_enum.js new file mode 100644 index 0000000..9651d44 --- /dev/null +++ b/frida/frida_scripts/raptor_android_enum.js @@ -0,0 +1,105 @@ +/* + * raptor_frida_android_enum.js - Java class/method enumerator + * Copyright (c) 2017 Marco Ivaldi + * + * Frida.re JS functions to enumerate Java classes and methods + * declared in an iOS app. See https://www.frida.re/ and + * https://codeshare.frida.re/ for further information on this + * powerful tool. + * + * "We want to help others achieve interop through reverse + * engineering" -- @oleavr + * + * Example usage: + * # frida -U -f com.target.app -l raptor_frida_android_enum.js --no-pause + * + * Get the latest version at: + * https://github.com/0xdea/frida-scripts/ + */ + +// enumerate all Java classes +function enumAllClasses() +{ + var allClasses = []; + var classes = Java.enumerateLoadedClassesSync(); + + classes.forEach(function(aClass) { + try { + var className = aClass.match(/[L](.*);/)[1].replace(/\//g, "."); + } + catch(err) {return;} // avoid TypeError: cannot read property 1 of null + allClasses.push(className); + }); + + return allClasses; +} + +// find all Java classes that match a pattern +function findClasses(pattern) +{ + var allClasses = enumAllClasses(); + var foundClasses = []; + + allClasses.forEach(function(aClass) { + try { + if (aClass.match(pattern)) { + foundClasses.push(aClass); + } + } + catch(err) {} // avoid TypeError: cannot read property 'match' of undefined + }); + + return foundClasses; +} + +// enumerate all methods declared in a Java class +function enumMethods(targetClass) +{ + var hook = Java.use(targetClass); + var ownMethods = hook.class.getDeclaredMethods(); + hook.$dispose; + + return ownMethods; +} + +/* + * The following functions were not implemented because deemed impractical: + * + * enumAllMethods() - enumerate all methods declared in all Java classes + * findMethods(pattern) - find all Java methods that match a pattern + * + * See raptor_frida_ios_enum.js for a couple of ObjC implementation examples. + */ + +// usage examples +setTimeout(function() { // avoid java.lang.ClassNotFoundException + + Java.perform(function() { + + // enumerate all classes + ///* + var a = enumAllClasses(); + a.forEach(function(s) { + console.log(s); + }); + //*/ + + // find classes that match a pattern + /* + var a = findClasses(/password/i); + a.forEach(function(s) { + console.log(s); + }); + */ + + // enumerate all methods in a class + /* + var a = enumMethods("com.target.app.PasswordManager") + a.forEach(function(s) { + console.log(s); + }); + */ + + }); +}, 0); + diff --git a/frida/frida_scripts/raptor_android_trace.js b/frida/frida_scripts/raptor_android_trace.js new file mode 100644 index 0000000..92a8d8b --- /dev/null +++ b/frida/frida_scripts/raptor_android_trace.js @@ -0,0 +1,183 @@ +/* + * raptor_frida_android_trace.js - Code tracer for Android + * Copyright (c) 2017 Marco Ivaldi + * + * Frida.re JS script to trace arbitrary Java Methods and + * Module functions for debugging and reverse engineering. + * See https://www.frida.re/ and https://codeshare.frida.re/ + * for further information on this powerful tool. + * + * "We want to help others achieve interop through reverse + * engineering" -- @oleavr + * + * Many thanks to @inode-, @federicodotta, @leonjza, and + * @dankluev. + * + * Example usage: + * # frida -U -f com.target.app -l raptor_frida_android_trace.js --no-pause + * + * Get the latest version at: + * https://github.com/0xdea/frida-scripts/ + */ + +// generic 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 (aClass.match(pattern)) { + found = true; + var className = aClass.match(/[L](.*);/)[1].replace(/\//g, "."); + traceClass(className); + } + }, + 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) { + parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]); + }); + + var targets = uniqBy(parsedMethods, JSON.stringify); + targets.forEach(function(targetMethod) { + 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 = hook[targetMethod].overloads.length; + + 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); + + // 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++) { + console.log("arg[" + j + "]: " + arguments[j]); + } + + // print retval + var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?) + 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); + }); +} + +// usage examples +setTimeout(function() { // avoid java.lang.ClassNotFoundException + + Java.perform(function() { + + // trace("com.target.utils.CryptoUtils.decrypt"); + // trace("com.target.utils.CryptoUtils"); + // trace("CryptoUtils"); + // trace(/crypto/i); + trace("exports:*!write*"); + + }); +}, 0); + diff --git a/frida/frida_scripts/ssl_pinning.js b/frida/frida_scripts/ssl_pinning.js new file mode 100644 index 0000000..b0d359b --- /dev/null +++ b/frida/frida_scripts/ssl_pinning.js @@ -0,0 +1,70 @@ +/* + Android SSL Re-pinning frida script v0.2 030417-pier + + $ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt + $ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pause + + https://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/ + + UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 ! +*/ + +setTimeout(function(){ + Java.perform(function (){ + console.log(""); + console.log("[.] Cert Pinning Bypass/Re-Pinning"); + + var CertificateFactory = Java.use("java.security.cert.CertificateFactory"); + var FileInputStream = Java.use("java.io.FileInputStream"); + var BufferedInputStream = Java.use("java.io.BufferedInputStream"); + var X509Certificate = Java.use("java.security.cert.X509Certificate"); + var KeyStore = Java.use("java.security.KeyStore"); + var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory"); + var SSLContext = Java.use("javax.net.ssl.SSLContext"); + + // Load CAs from an InputStream + console.log("[+] Loading our CA...") + var cf = CertificateFactory.getInstance("X.509"); + + try { + var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt"); + } + catch(err) { + console.log("[o] " + err); + } + + var bufferedInputStream = BufferedInputStream.$new(fileInputStream); + var ca = cf.generateCertificate(bufferedInputStream); + bufferedInputStream.close(); + + var certInfo = Java.cast(ca, X509Certificate); + console.log("[o] Our CA Info: " + certInfo.getSubjectDN()); + + // Create a KeyStore containing our trusted CAs + console.log("[+] Creating a KeyStore for our CA..."); + var keyStoreType = KeyStore.getDefaultType(); + var keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore..."); + var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + var tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + console.log("[+] Our TrustManager is ready..."); + + console.log("[+] Hijacking SSLContext methods now...") + console.log("[-] Waiting for the app to invoke SSLContext.init()...") + + SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) { + console.log("[o] App invoked javax.net.ssl.SSLContext.init..."); + SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c); + console.log("[+] SSLContext initialized with our custom TrustManager!"); + } + }); +},0); + + + + diff --git a/frida/frida_scripts/trace.js b/frida/frida_scripts/trace.js new file mode 100644 index 0000000..eaeb489 --- /dev/null +++ b/frida/frida_scripts/trace.js @@ -0,0 +1,3 @@ +Java.perform(function() { + console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())) +}); diff --git a/frida/frida_start.sh b/frida/frida_start.sh new file mode 100755 index 0000000..e6aeabb --- /dev/null +++ b/frida/frida_start.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# + +printf "Starting frida server..\n\n" + + +adb shell /data/local/tmp/frida-server & + + + diff --git a/frida/get_frida_server.sh b/frida/get_frida_server.sh new file mode 100755 index 0000000..8657d56 --- /dev/null +++ b/frida/get_frida_server.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# + + + + +printf "\nChecking processor type of attached Android..\n\n" + + +adb shell getprop | grep abi + + + +printf "\nNEXT: \nDownload matching frida-server from: https://github.com/frida/frida/releases\n" + +printf "\n\nThen extract frida-server and push to Android with: \n" + +printf "adb push ./frida-server /data/local/tmp\nadb shell chmod +x /data/local/tmp/frida-server\n" + +# adb push ./frida-server /data/local/tmp +# adb shell chmod +x /data/local/tmp/frida-server + + + + diff --git a/frida/install_frida.sh b/frida/install_frida.sh new file mode 100755 index 0000000..0a83e42 --- /dev/null +++ b/frida/install_frida.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# + + +printf "Installing frida tools and python venv..\n\n" + +sudo apt install python3-venv + +pip install frida-tools + + + +printf "\nChecking frida version..\n" + +frida --version + + diff --git a/frida/tracers/trace_opens.sh b/frida/tracers/trace_opens.sh new file mode 100755 index 0000000..9de5802 --- /dev/null +++ b/frida/tracers/trace_opens.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# + +frida-trace -U -i open Mi\ Claro + + + diff --git a/frida/tracers/trace_recvs.sh b/frida/tracers/trace_recvs.sh new file mode 100755 index 0000000..7c80f8a --- /dev/null +++ b/frida/tracers/trace_recvs.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# + +frida-trace -U -i recvm* Mi\ Telcel + + + diff --git a/frida/tracers/trace_sends.sh b/frida/tracers/trace_sends.sh new file mode 100755 index 0000000..909baee --- /dev/null +++ b/frida/tracers/trace_sends.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# + +frida-trace -U -i send* Mi\ Telcel + + + diff --git a/grab_apk.sh b/grab_apk.sh new file mode 100755 index 0000000..a43af37 --- /dev/null +++ b/grab_apk.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# + + +# adb shell pm list packages + +#BUNDLE_ID="com.duckduckgo.mobile.android" +BUNDLE_ID=$1 + + +mkdir -p ./apks + +printf "Downloading bundle id: $BUNDLE_ID\n\n" + +APK=$(adb shell pm path $BUNDLE_ID) +printf "\nAPK path on device: $APK\n" + +APK=`echo $APK | awk '{print $NF}' FS=':' | tr -d '\r\n'` +printf "\napk file: $APK\n" + +NEW_APK_FP="./apks/$BUNDLE_ID.apk" + +adb pull $APK $NEW_APK_FP +printf "\nDownloaded apk file: $NEW_APK_FP\n" diff --git a/install_all.sh b/install_all.sh new file mode 100755 index 0000000..2a08d54 --- /dev/null +++ b/install_all.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# + + + +printf "Installing mitmproxy binaries and genymotion..\n\n" + +wget https://snapshots.mitmproxy.org/9.0.1/mitmproxy-9.0.1-linux.tar.gz -O mitmproxy.tar.gz + + +mkdir -p ./mitmprox +tar -xvf ./mitmproxy.tar.gz -C ./mitmprox + +rm -v mitmproxy.tar.gz +printf "\nInstalled mitmproxy binaries at ./mitmprox\n" + + +wget https://dl.genymotion.com/releases/genymotion-3.1.2/genymotion-3.1.2-linux_x64.bin +chmod +x ./genymotion-3.1.2-linux_x64.bin +./genymotion-3.1.2-linux_x64.bin --yes + + + diff --git a/jadx/install_jadx.sh b/jadx/install_jadx.sh new file mode 100755 index 0000000..708645a --- /dev/null +++ b/jadx/install_jadx.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# + + +printf "\nInstalling Jadx..\n" + + +git clone https://github.com/skylot/jadx.git + + +cd jadx + +./gradlew dist + + +#export PATH=$PATH:path/to/build/jadx/bin/jadx + + + + diff --git a/setup_scripts/install_mobsf.sh b/setup_scripts/install_mobsf.sh new file mode 100755 index 0000000..c8506b4 --- /dev/null +++ b/setup_scripts/install_mobsf.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# + + + +printf "\n\nInstalling mobsf framework..\n" + +sudo apt install python3.8-venv -y + +git clone https://github.com/MobSF/Mobile-Security-Framework-MobSF.git +cd Mobile-Security-Framework-MobSF +./setup.sh + + + diff --git a/setup_scripts/install_prereqs.sh b/setup_scripts/install_prereqs.sh new file mode 100755 index 0000000..97e2f2f --- /dev/null +++ b/setup_scripts/install_prereqs.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# + + + + +printf "Installing prereqs including python3, pip, dev-tools, and apktool..\n" + + +sudo apt install python3 -y + +sudo apt install python3-pip -y + +sudo apt install build-essential -y + + +sudo apt install apktool -y diff --git a/setup_scripts/make_root_ca.sh b/setup_scripts/make_root_ca.sh new file mode 100755 index 0000000..6805ab3 --- /dev/null +++ b/setup_scripts/make_root_ca.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# + + +printf "Copying mitm cert to root certs on Android...\n\n" + + +mkdir -p certs_mitm +cd ./certs_mitm + +cp -v ~/.mitmproxy/mitmproxy-ca-cert.cer . + +hashed_name=`openssl x509 -inform PEM -subject_hash_old -in mitmproxy-ca-cert.cer | head -1` && cp mitmproxy-ca-cert.cer $hashed_name.0 + + +adb remount +adb push $hashed_name.0 /etc/security/cacerts/ + +adb shell chmod 644 /etc/security/cacerts/$hashed_name.0 +adb reboot + +printf "\nFinished copying mitm cert to system certs on Android\n\n" + + diff --git a/start_all.sh b/start_all.sh new file mode 100755 index 0000000..c06bcd4 --- /dev/null +++ b/start_all.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# + + +printf "Stopping genymotion and mitmproxy tools...\n\n" + + + +./mitmprox/mitmweb & + +./genymotion/genymotion & + + diff --git a/stop_all.sh b/stop_all.sh new file mode 100755 index 0000000..8aa4e5b --- /dev/null +++ b/stop_all.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# + + +printf "Stopping genymotion and mitmproxy tools...\n\n" + + +pkill genymotion + +pkill mitmweb + + + +