Beau Kujath
2 years ago
36 changed files with 3123 additions and 1 deletions
-
54README.md
-
38frida/__handlers__/libc.so/open.js
-
38frida/__handlers__/libc.so/send.js
-
38frida/__handlers__/libc.so/sendfile.js
-
38frida/__handlers__/libc.so/sendmmsg.js
-
38frida/__handlers__/libc.so/sendmsg.js
-
38frida/__handlers__/libc.so/sendto.js
-
11frida/col_pin_pass.sh
-
28frida/frida_scripts/classers.js
-
5frida/frida_scripts/enumerate_methods.js
-
455frida/frida_scripts/file_access.js
-
66frida/frida_scripts/frida_certpin_pass.js
-
251frida/frida_scripts/hook-finder.js
-
148frida/frida_scripts/http-connection.js
-
731frida/frida_scripts/multiple_unpinning.js
-
1frida/frida_scripts/other-enumerate.js
-
523frida/frida_scripts/pass2.js
-
38frida/frida_scripts/printStrings.js
-
105frida/frida_scripts/raptor_android_enum.js
-
183frida/frida_scripts/raptor_android_trace.js
-
70frida/frida_scripts/ssl_pinning.js
-
3frida/frida_scripts/trace.js
-
11frida/frida_start.sh
-
25frida/get_frida_server.sh
-
17frida/install_frida.sh
-
7frida/tracers/trace_opens.sh
-
7frida/tracers/trace_recvs.sh
-
7frida/tracers/trace_sends.sh
-
24grab_apk.sh
-
23install_all.sh
-
20jadx/install_jadx.sh
-
15setup_scripts/install_mobsf.sh
-
17setup_scripts/install_prereqs.sh
-
24setup_scripts/make_root_ca.sh
-
13start_all.sh
-
14stop_all.sh
@ -1,3 +1,55 @@ |
|||
# relab |
|||
Lab for reverse engineering APKs |
|||
|
|||
RE env for inspecting APKs |
|||
|
|||
### 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 <package_id> --no-pause` |
|||
4. Trace files being opened by app on device: `frida-trace -U -i open -f <package_id>` |
|||
|
|||
|
|||
_**Note:** Most Android apps do not need SSL pinning bypass for mitmproxy to work_ |
@ -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) { |
|||
} |
|||
} |
@ -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) { |
|||
} |
|||
} |
@ -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) { |
|||
} |
|||
} |
@ -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) { |
|||
} |
|||
} |
@ -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) { |
|||
} |
|||
} |
@ -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) { |
|||
} |
|||
} |
@ -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 |
|||
|
|||
|
|||
|
@ -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<obj.length;i++){ |
|||
console.log("obj" +"::"+i) |
|||
var common =JSON.stringify(obj[i].classes[i],null," ") |
|||
console.log("\x1b[32m","class-name "+" "+JSON.parse(common).name) |
|||
var x; |
|||
for (x=0;x<JSON.parse(common).methods.length;x++) |
|||
{ |
|||
//console.log("methods"+ "::>"+x)
|
|||
//console.log("df")
|
|||
console.log("\x1b[34m",JSON.parse(common).methods[x]) |
|||
|
|||
|
|||
} |
|||
|
|||
} |
|||
}); |
@ -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)); |
|||
}); |
@ -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]<File:" + file.hashCode() + ">"; |
|||
|
|||
|
|||
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; |
|||
} |
|||
|
|||
}); |
|||
|
@ -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); |
@ -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, <tim -at- corellium.com> <diff -at- protonmail.com>
|
|||
// 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); |
|||
}); |
@ -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); |
|||
}; |
|||
}); |
|||
}); |
@ -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<v3] (double bypass) //
|
|||
////////////////////////////////////////////////////////////
|
|||
try { |
|||
// Bypass Squareup CertificatePinner {1}
|
|||
var Squareup_CertificatePinner_Activity_1 = Java.use('com.squareup.okhttp.CertificatePinner'); |
|||
Squareup_CertificatePinner_Activity_1.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function(a, b) { |
|||
console.log('[+] Bypassing Squareup CertificatePinner {1}: ' + a); |
|||
return; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Squareup CertificatePinner {1} pinner not found'); |
|||
//console.log(err);
|
|||
} |
|||
try { |
|||
// Bypass Squareup CertificatePinner {2}
|
|||
var 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 {2}: ' + a); |
|||
return; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Squareup CertificatePinner {2} pinner not found'); |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Squareup OkHostnameVerifier [OkHTTP v3] (double bypass) //
|
|||
/////////////////////////////////////////////////////////////
|
|||
try { |
|||
// Bypass Squareup OkHostnameVerifier {1}
|
|||
var 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 {1}: ' + a); |
|||
return true; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Squareup OkHostnameVerifier check not found'); |
|||
//console.log(err);
|
|||
} |
|||
try { |
|||
// Bypass Squareup OkHostnameVerifier {2}
|
|||
var 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 {2}: ' + a); |
|||
return true; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Squareup OkHostnameVerifier check not found'); |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Android WebViewClient (quadruple bypass) //
|
|||
//////////////////////////////////////////////
|
|||
try { |
|||
// Bypass WebViewClient {1} (deprecated from Android 6)
|
|||
var 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 check {1}'); |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Android WebViewClient {1} check not found'); |
|||
//console.log(err)
|
|||
} |
|||
try { |
|||
// Bypass WebViewClient {2}
|
|||
var 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 check {2}'); |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Android WebViewClient {2} check not found'); |
|||
//console.log(err)
|
|||
} |
|||
try { |
|||
// Bypass WebViewClient {3}
|
|||
var AndroidWebViewClient_Activity_3 = Java.use('android.webkit.WebViewClient'); |
|||
AndroidWebViewClient_Activity_3.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(obj1, obj2, obj3, obj4) { |
|||
console.log('[+] Bypassing Android WebViewClient check {3}'); |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Android WebViewClient {3} check not found'); |
|||
//console.log(err)
|
|||
} |
|||
try { |
|||
// Bypass WebViewClient {4}
|
|||
var AndroidWebViewClient_Activity_4 = Java.use('android.webkit.WebViewClient'); |
|||
AndroidWebViewClient_Activity_4.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function(obj1, obj2, obj3) { |
|||
console.log('[+] Bypassing Android WebViewClient check {4}'); |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Android WebViewClient {4} check not found'); |
|||
//console.log(err)
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Apache Cordova WebViewClient //
|
|||
//////////////////////////////////
|
|||
try { |
|||
var 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 check'); |
|||
obj3.proceed(); |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Apache Cordova WebViewClient check not found'); |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Boye AbstractVerifier //
|
|||
///////////////////////////
|
|||
try { |
|||
var boye_AbstractVerifier = Java.use('ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier'); |
|||
boye_AbstractVerifier.verify.implementation = function(host, ssl) { |
|||
console.log('[+] Bypassing Boye AbstractVerifier check: ' + host); |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Boye AbstractVerifier check not found'); |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Apache AbstractVerifier //
|
|||
/////////////////////////////
|
|||
try { |
|||
var apache_AbstractVerifier = Java.use('org.apache.http.conn.ssl.AbstractVerifier'); |
|||
apache_AbstractVerifier.verify.implementation = function(a, b, c, d) { |
|||
console.log('[+] Bypassing Apache AbstractVerifier check: ' + a); |
|||
return; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Apache AbstractVerifier check not found'); |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Chromium Cronet //
|
|||
/////////////////////
|
|||
try { |
|||
var CronetEngineBuilderImpl_Activity = Java.use("org.chromium.net.impl.CronetEngineBuilderImpl"); |
|||
// Setting argument to TRUE (default is TRUE) to disable Public Key pinning for local trust anchors
|
|||
CronetEngine_Activity.enablePublicKeyPinningBypassForLocalTrustAnchors.overload('boolean').implementation = function(a) { |
|||
console.log("[+] Disabling Public Key pinning for local trust anchors in Chromium Cronet"); |
|||
var cronet_obj_1 = CronetEngine_Activity.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true); |
|||
return cronet_obj_1; |
|||
}; |
|||
// Bypassing Chromium Cronet pinner
|
|||
CronetEngine_Activity.addPublicKeyPins.overload('java.lang.String', 'java.util.Set', 'boolean', 'java.util.Date').implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) { |
|||
console.log("[+] Bypassing Chromium Cronet pinner: " + hostName); |
|||
var cronet_obj_2 = CronetEngine_Activity.addPublicKeyPins.call(this, hostName, pinsSha256, includeSubdomains, expirationDate); |
|||
return cronet_obj_2; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Chromium Cronet pinner not found') |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
// Flutter Pinning packages http_certificate_pinning and ssl_pinning_plugin (double bypass) //
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////
|
|||
try { |
|||
// Bypass HttpCertificatePinning.check {1}
|
|||
var HttpCertificatePinning_Activity = Java.use('diefferson.http_certificate_pinning.HttpCertificatePinning'); |
|||
HttpCertificatePinning_Activity.checkConnexion.overload("java.lang.String", "java.util.List", "java.util.Map", "int", "java.lang.String").implementation = function (a, b, c ,d, e) { |
|||
console.log('[+] Bypassing Flutter HttpCertificatePinning : ' + a); |
|||
return true; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Flutter HttpCertificatePinning pinner not found'); |
|||
//console.log(err);
|
|||
} |
|||
try { |
|||
// Bypass SslPinningPlugin.check {2}
|
|||
var SslPinningPlugin_Activity = Java.use('com.macif.plugin.sslpinningplugin.SslPinningPlugin'); |
|||
SslPinningPlugin_Activity.checkConnexion.overload("java.lang.String", "java.util.List", "java.util.Map", "int", "java.lang.String").implementation = function (a, b, c ,d, e) { |
|||
console.log('[+] Bypassing Flutter SslPinningPlugin: ' + a); |
|||
return true; |
|||
}; |
|||
} catch (err) { |
|||
console.log('[-] Flutter SslPinningPlugin pinner not found'); |
|||
//console.log(err);
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
// Dynamic SSLPeerUnverifiedException Patcher //
|
|||
// An useful technique to bypass SSLPeerUnverifiedException failures raising //
|
|||
// when the Android app uses some uncommon SSL Pinning methods or an heavily //
|
|||
// code obfuscation. Inspired by an idea of: https://github.com/httptoolkit //
|
|||
///////////////////////////////////////////////////////////////////////////////
|
|||
function rudimentaryFix(typeName) { |
|||
// This is a improvable rudimentary fix, if not works you can patch it manually
|
|||
if (typeName === undefined){ |
|||
return; |
|||
} else if (typeName === 'boolean') { |
|||
return true; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
try { |
|||
var UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException'); |
|||
UnverifiedCertError.$init.implementation = function (str) { |
|||
console.log('\x1b[36m[!] Unexpected SSLPeerUnverifiedException occurred, trying to patch it dynamically...\x1b[0m'); |
|||
try { |
|||
var stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace(); |
|||
var exceptionStackIndex = stackTrace.findIndex(stack => |
|||
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<splittedList.length; i++) { |
|||
var extractedOverload = splittedList[i].trim().split("(")[1].slice(0,-1).replaceAll("'",""); |
|||
// Check if extractedOverload has multiple arguments
|
|||
if (extractedOverload.includes(",")) { |
|||
// Go here if overloaded method has multiple arguments (NOTE: max 6 args are covered here)
|
|||
var argList = extractedOverload.split(", "); |
|||
console.log('\x1b[36m[!] Attempting overload of '+className+'.'+methodName+' with arguments: '+extractedOverload+'\x1b[0m'); |
|||
if (argList.length == 2) { |
|||
callingMethod.overload(argList[0], argList[1]).implementation = function(a,b) { |
|||
rudimentaryFix(returnTypeName); |
|||
} |
|||
} else if (argNum == 3) { |
|||
callingMethod.overload(argList[0], argList[1], argList[2]).implementation = function(a,b,c) { |
|||
rudimentaryFix(returnTypeName); |
|||
} |
|||
} else if (argNum == 4) { |
|||
callingMethod.overload(argList[0], argList[1], argList[2], argList[3]).implementation = function(a,b,c,d) { |
|||
rudimentaryFix(returnTypeName); |
|||
} |
|||
} else if (argNum == 5) { |
|||
callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4]).implementation = function(a,b,c,d,e) { |
|||
rudimentaryFix(returnTypeName); |
|||
} |
|||
} else if (argNum == 6) { |
|||
callingMethod.overload(argList[0], argList[1], argList[2], argList[3], argList[4], argList[5]).implementation = function(a,b,c,d,e,f) { |
|||
rudimentaryFix(returnTypeName); |
|||
} |
|||
} |
|||
// Go here if overloaded method has a single argument
|
|||
} else { |
|||
callingMethod.overload(extractedOverload).implementation = function(a) { |
|||
rudimentaryFix(returnTypeName); |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
console.log('\x1b[36m[-] Failed to dynamically patch SSLPeerUnverifiedException '+e+'\x1b[0m'); |
|||
} |
|||
} |
|||
//console.log('\x1b[36m[+] SSLPeerUnverifiedException hooked\x1b[0m');
|
|||
return this.$init(str); |
|||
}; |
|||
} catch (err) { |
|||
//console.log('\x1b[36m[-] SSLPeerUnverifiedException not found\x1b[0m');
|
|||
//console.log('\x1b[36m'+err+'\x1b[0m');
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
}); |
|||
|
|||
}, 0); |
|||
|
@ -0,0 +1 @@ |
|||
Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) },"onComplete":function(){}})}) |
@ -0,0 +1,523 @@ |
|||
/* |
|||
* This script combines, fixes & extends a long list of other scripts, most notably including: |
|||
* |
|||
* - https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/
|
|||
* - https://codeshare.frida.re/@avltree9798/universal-android-ssl-pinning-bypass/
|
|||
* - https://pastebin.com/TVJD63uM
|
|||
*/ |
|||
|
|||
setTimeout(function () { |
|||
Java.perform(function () { |
|||
console.log("---"); |
|||
console.log("Unpinning Android app..."); |
|||
|
|||
/// -- Generic hook to protect against SSLPeerUnverifiedException -- ///
|
|||
|
|||
// In some cases, with unusual cert pinning approaches, or heavy obfuscation, we can't
|
|||
// match the real method & package names. This is a problem! Fortunately, we can still
|
|||
// always match built-in types, so here we spot all failures that use the built-in cert
|
|||
// error type (notably this includes OkHttp), and after the first failure, we dynamically
|
|||
// generate & inject a patch to completely disable the method that threw the error.
|
|||
try { |
|||
const UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException'); |
|||
UnverifiedCertError.$init.implementation = function (str) { |
|||
console.log(' --> 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<v3] (double bypass)
|
|||
try { |
|||
// Bypass Squareup CertificatePinner {1}
|
|||
const Squareup_CertificatePinner_Activity_1 = Java.use('com.squareup.okhttp.CertificatePinner'); |
|||
Squareup_CertificatePinner_Activity_1.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function (a, b) { |
|||
console.log(' --> 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); |
|||
|
@ -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); |
@ -0,0 +1,105 @@ |
|||
/* |
|||
* raptor_frida_android_enum.js - Java class/method enumerator |
|||
* Copyright (c) 2017 Marco Ivaldi <raptor@0xdeadbeef.info> |
|||
* |
|||
* 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); |
|||
|
@ -0,0 +1,183 @@ |
|||
/* |
|||
* raptor_frida_android_trace.js - Code tracer for Android |
|||
* Copyright (c) 2017 Marco Ivaldi <raptor@0xdeadbeef.info> |
|||
* |
|||
* 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); |
|||
|
@ -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); |
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,3 @@ |
|||
Java.perform(function() { |
|||
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())) |
|||
}); |
@ -0,0 +1,11 @@ |
|||
#!/bin/bash |
|||
|
|||
# |
|||
|
|||
printf "Starting frida server..\n\n" |
|||
|
|||
|
|||
adb shell /data/local/tmp/frida-server & |
|||
|
|||
|
|||
|
@ -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 |
|||
|
|||
|
|||
|
|||
|
@ -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 |
|||
|
|||
|
@ -0,0 +1,7 @@ |
|||
#!/bin/bash |
|||
# |
|||
|
|||
frida-trace -U -i open Mi\ Claro |
|||
|
|||
|
|||
|
@ -0,0 +1,7 @@ |
|||
#!/bin/bash |
|||
# |
|||
|
|||
frida-trace -U -i recvm* Mi\ Telcel |
|||
|
|||
|
|||
|
@ -0,0 +1,7 @@ |
|||
#!/bin/bash |
|||
# |
|||
|
|||
frida-trace -U -i send* Mi\ Telcel |
|||
|
|||
|
|||
|
@ -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" |
@ -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 |
|||
|
|||
|
|||
|
@ -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 |
|||
|
|||
|
|||
|
|||
|
@ -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 |
|||
|
|||
|
|||
|
@ -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 |
@ -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" |
|||
|
|||
|
@ -0,0 +1,13 @@ |
|||
#!/bin/bash |
|||
# |
|||
|
|||
|
|||
printf "Stopping genymotion and mitmproxy tools...\n\n" |
|||
|
|||
|
|||
|
|||
./mitmprox/mitmweb & |
|||
|
|||
./genymotion/genymotion & |
|||
|
|||
|
@ -0,0 +1,14 @@ |
|||
#!/bin/bash |
|||
# |
|||
|
|||
|
|||
printf "Stopping genymotion and mitmproxy tools...\n\n" |
|||
|
|||
|
|||
pkill genymotion |
|||
|
|||
pkill mitmweb |
|||
|
|||
|
|||
|
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue