├── .gitignore ├── Fun_With_Frida_Public.pdf ├── README.md ├── instrument-apk.sh ├── setup-frida-android.sh ├── tasks ├── 0 │ ├── intro.js │ ├── intro.py │ ├── task0.txt │ ├── test │ └── test.c ├── 1 │ └── task1.txt ├── 2 │ ├── task2.txt │ ├── text.js │ └── text.py ├── 3 │ ├── pinned_ssl.js │ ├── pinning.apk │ ├── task3.txt │ └── unpinned_ssl.js ├── enumeration │ ├── enumerate_classes.js │ └── enumerate_classes.py ├── funwithfrida │ ├── funwithfrida1.apk │ └── funwithfrida2.apk └── needleRemover │ ├── pinning.apk │ └── src │ └── space │ └── polylog │ └── owasp │ └── needleremover │ ├── CounterActivity.java │ ├── MainActivity.java │ ├── Native_Call.java │ ├── SSLActivity.java │ ├── TextActivity.java │ └── networking │ ├── NetworkFragment.java │ └── TLSDownloadCallback.java ├── unpack-apk.sh └── update-frida.sh /.gitignore: -------------------------------------------------------------------------------- 1 | frida-libs/ 2 | -------------------------------------------------------------------------------- /Fun_With_Frida_Public.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pspace/bsidesmuc/baaf0c1e315e8c8c14e58512f219993ea577e52c/Fun_With_Frida_Public.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Solutions and material for our BSides Munich Frida Workshop 2 | 3 | For questions and remarks you can find us on Twitter: 4 | - [@c0dmtr1x](https://twitter.com/c0dmtr1x) 5 | - [@pspacecomplete](https://twitter.com/pspacecomplete) 6 | -------------------------------------------------------------------------------- /instrument-apk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #https://koz.io/using-frida-on-android-without-root/ 3 | CWD=`pwd` 4 | DOCSTRING="Usage: ./instrument-apk.sh -p PROJECTDIR -k PATH/TO/KEYSTORE -s ALIAS_FOR_SIGNING_KEY" 5 | REBUILD_ONLY=0 6 | while [ "$1" != "" ]; 7 | do 8 | case $1 in 9 | -r |--rebuild-only ) 10 | echo "REBUILD ONLY mode" 11 | REBUILD_ONLY=1 12 | ;; 13 | -p |--project ) 14 | shift 15 | PROJECTDIR="$1" 16 | ;; 17 | -k |--keystore ) 18 | shift 19 | KEYSTORE="$1" 20 | ;; 21 | -a |--keyalias ) 22 | shift 23 | KEYALIAS="$1" 24 | ;; 25 | -h|--help) 26 | echo "$DOCSTRING" 27 | exit 28 | ;; 29 | *) 30 | echo $1 31 | echo "$DOCSTRING" 32 | exit 33 | ;; 34 | esac 35 | shift 36 | done 37 | 38 | if [ ! -d $PROJECTDIR ]; then 39 | echo "APK project directory not found! Exiting!" 40 | exit -1 41 | fi 42 | BASENAME=`basename $PROJECTDIR` 43 | 44 | EXTRACT_DIR="$PROJECTDIR/extracted" 45 | FRIDA_LIBDIR="$CWD/frida-libs" 46 | 47 | if [ ! -f "$EXTRACT_DIR/AndroidManifest.xml" ]; then 48 | echo "No extracted APK found in $EXTRACT_DIR - Exiting!" 49 | exit -2 50 | fi 51 | 52 | 53 | echo "Copying gadget library" 54 | VERSION=`cat "$FRIDA_LIBDIR/LATEST_RELEASE"` 55 | GADGET_LIB=libfrida-gadget.so 56 | ARCH="arm64" 57 | DST="$EXTRACT_DIR/lib/arm64-v8a" 58 | FRIDA_LIB="$FRIDA_LIBDIR/frida-gadget-$VERSION-android-$ARCH.so" 59 | mkdir -p "$DST" 60 | cp "$FRIDA_LIB" "$DST"/$GADGET_LIB 61 | 62 | ARCH="arm" 63 | DST="$EXTRACT_DIR/lib/armeabi" 64 | FRIDA_LIB="$FRIDA_LIBDIR/frida-gadget-$VERSION-android-$ARCH.so" 65 | mkdir -p "$DST" 66 | cp "$FRIDA_LIB" "$DST"/$GADGET_LIB 67 | DST="$EXTRACT_DIR/lib/armeabi-v7a" 68 | FRIDA_LIB="$FRIDA_LIBDIR/frida-gadget-$VERSION-android-$ARCH.so" 69 | mkdir -p "$DST" 70 | cp "$FRIDA_LIB" "$DST"/$GADGET_LIB 71 | 72 | 73 | ARCH="x86" 74 | DST="$EXTRACT_DIR/lib/x86" 75 | FRIDA_LIB="$FRIDA_LIBDIR/frida-gadget-$VERSION-android-$ARCH.so" 76 | mkdir -p "$DST" 77 | cp "$FRIDA_LIB" "$DST"/$GADGET_LIB 78 | 79 | ARCH="arm" 80 | DST="$EXTRACT_DIR/lib/x86_64" 81 | FRIDA_LIB="$FRIDA_LIBDIR/frida-gadget-$VERSION-android-$ARCH.so" 82 | mkdir -p "$DST" 83 | cp "$FRIDA_LIB" "$DST"/$GADGET_LIB 84 | 85 | 86 | echo "Please add or update the library injection ('System.loadLibrary(\"frida-gadget\")') to the code (use another terminal window :-) )." 87 | echo "E.g." 88 | echo "const-string v0, \"frida-gadget\"" 89 | echo "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V" 90 | echo "in a static initializer." 91 | echo 92 | echo "Grant frida permissions to open a socket by adding " 93 | echo "" 94 | echo "somewhere before the tag." 95 | echo 96 | read -p "Press enter to continue after you have made the required changes. The script will continue to build the instrumented APK..." 97 | 98 | TMP_APK=$PROJECTDIR/tmp-$BASENAME.apk 99 | echo "Repackging APK into: $TMP_APK" 100 | rm $TMP_APK 101 | apktool b -o $TMP_APK $EXTRACT_DIR/ 102 | 103 | echo "Signing the APK. If you already have a signing key just press Enter to continue. If not create a new key via:" 104 | echo "keytool -genkey -v -keystore $KEYSTORE -alias $KEYALIAS -keyalg RSA -keysize 2048 -validity 8192" 105 | read -p "Press enter to continue..." 106 | 107 | echo "Creating signature" 108 | echo Executing: jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore $KEYSTORE $TMP_APK $KEYALIAS 109 | jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore $KEYSTORE $TMP_APK $KEYALIAS 110 | 111 | 112 | NEW_APK="$PROJECTDIR/frdrd-$BASENAME.apk" 113 | echo "Checking signature" 114 | jarsigner -verify $TMP_APK 115 | 116 | echo "Done" 117 | 118 | 119 | echo "Zipaligning the new APK" 120 | rm $NEW_APK 121 | zipalign 4 $TMP_APK $NEW_APK 122 | 123 | echo 124 | echo "Install with" 125 | echo "adb install -r $NEW_APK" 126 | -------------------------------------------------------------------------------- /setup-frida-android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DOCSTRING="Usage: ./setup-frida-android.sh -a TARGET_ARCHITECTURE [-b DIR_WITH_DOWNLOADED_BINARIES] [-n BINARY_NAME_ON_TARGET]" 3 | NAME='frida-server' 4 | 5 | ARCH='invalid' 6 | BINDIR='frida-libs' 7 | 8 | while [ "$1" != "" ]; 9 | do 10 | case $1 in 11 | -a |--arch ) 12 | shift 13 | ARCH=$1 14 | ;; 15 | -b |--bindir ) 16 | shift 17 | BINDIR=$1 18 | ;; 19 | -n|--name) 20 | shift 21 | NAME=$1 22 | ;; 23 | -h|--help) 24 | echo $DOCSTRING 25 | exit 26 | ;; 27 | *) 28 | echo $1 29 | echo $DOCSTRING 30 | exit 31 | ;; 32 | esac 33 | shift 34 | done 35 | 36 | if [[ "$ARCH" = "invalid" ]]; then 37 | echo "No target architecture specified! $ARCH" 38 | echo $DOCSTRING 39 | exit -1 40 | fi 41 | 42 | if [[ -z "$NAME" ]]; then 43 | NAME='frida' 44 | fi 45 | 46 | VERSION=`cat $BINDIR/LATEST_RELEASE` 47 | 48 | # Switch adb to root - this is required for the server binary to access other processes' memory 49 | echo "Executing: adb root" 50 | adb root 51 | 52 | echo "Executing: adb push $BINDIR/frida-server-$VERSION-android-$ARCH /data/local/tmp/$NAME" 53 | adb push $BINDIR/frida-server-$VERSION-android-$ARCH /data/local/tmp/$NAME 54 | 55 | echo Executing: adb shell "chmod 755 /data/local/tmp/$NAME" 56 | adb shell "su 0 chmod 755 /data/local/tmp/$NAME" 57 | 58 | echo Executing: adb shell "/data/local/tmp/$NAME &" 59 | adb shell "/data/local/tmp/$NAME" & 60 | 61 | # check if everything worked 62 | frida-ps -U 63 | -------------------------------------------------------------------------------- /tasks/0/intro.js: -------------------------------------------------------------------------------- 1 | Interceptor.attach(ptr("ADDRESS"), { 2 | onEnter: function(args) { 3 | args[0] = ptr("1338"); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /tasks/0/intro.py: -------------------------------------------------------------------------------- 1 | # ######################### PYTHON ################## 2 | import frida 3 | import sys 4 | 5 | # Attach to binary 6 | session = frida.attach("test") 7 | # read JS payload from file 8 | js_content = open("intro.js").read() 9 | 10 | address = sys.argv[1] 11 | js_content_with_address = js_content.replace("ADDRESS", address) 12 | 13 | # init Frida script with JS 14 | script = session.create_script(js_content_with_address) 15 | 16 | # Start & keep running 17 | script.load() 18 | sys.stdin.read() -------------------------------------------------------------------------------- /tasks/0/task0.txt: -------------------------------------------------------------------------------- 1 | Just try to understand & reproduce the example from the slides. 2 | -------------------------------------------------------------------------------- /tasks/0/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pspace/bsidesmuc/baaf0c1e315e8c8c14e58512f219993ea577e52c/tasks/0/test -------------------------------------------------------------------------------- /tasks/0/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void 5 | f (int n) 6 | { 7 | printf ("Number: %d\n", n); 8 | } 9 | 10 | int main (int argc, char * argv[]) 11 | { 12 | int i = 0; 13 | printf("f() is at %p\n", f); 14 | while (1) 15 | { 16 | f (i++); 17 | sleep (1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tasks/1/task1.txt: -------------------------------------------------------------------------------- 1 | Experiment with frida-trace on Firefox. Try to intercept calls to a function e.g. PR_Read. 2 | 3 | Hint: in Firefox you can modify resend requests. Try to remove compression for the request to get human readable results. 4 | 5 | Frida's Javascript API: https://www.frida.re/docs/javascript-api/ 6 | -------------------------------------------------------------------------------- /tasks/2/task2.txt: -------------------------------------------------------------------------------- 1 | Use the "needleREmover" app, and try to intercept and modify the text set in the TextActivity. 2 | 3 | Sources are in /home/frida/tasks/needleRemover/src/space/polylog/owasp/needleremover/TextActivity.java 4 | For an example how sending/receiving works check: /home/frida/tasks/enumeration/* -------------------------------------------------------------------------------- /tasks/2/text.js: -------------------------------------------------------------------------------- 1 | 'use strict;' 2 | // Start with checking if a Java environment is available 3 | if (Java.available) { 4 | 5 | // all Java handling needs to happen inside a function passed to Java.perform() 6 | Java.perform(function() { 7 | 8 | // JS wrappers for Java classes 9 | const JavaStringClass = Java.use('java.lang.String'); 10 | const TextActivity = Java.use("space.polylog.owasp.needleremover.TextActivity"); 11 | 12 | // overwrite the method responsible for setting the text which will be displayed 13 | TextActivity.setPanelContent.implementation = function(newContent) { 14 | // send the argument that the method originally received to the python module 15 | send(newContent); 16 | 17 | // declare variable for the response 18 | var user_defined_content = "DEFAULT"; 19 | 20 | 21 | /* 22 | See the docs about the message passing: https://www.frida.re/docs/messages/ 23 | `recv` provides an asynchronous mechanism to handle messages from the controller side. 24 | Remember: messages are JSON strings of the format {u'type': '', }. 25 | In our case: 26 | {'type': 'input', 'payload': } 27 | The first argument is the `type` this recv-instance is waiting for, 28 | and the second argument is a function that is invoked with the 29 | received JSON content 30 | */ 31 | var op = recv('input', function onMessage(value) { 32 | user_defined_content = value.payload; 33 | } 34 | ); 35 | // block until an answer arrives 36 | op.wait(); 37 | 38 | // Create a java.lang.String instance with the new content 39 | var jstring = JavaStringClass.$new(user_defined_content); 40 | // and pass it to the original implementation of the method 41 | this.setPanelContent(jstring); 42 | } 43 | } 44 | ) 45 | } -------------------------------------------------------------------------------- /tasks/2/text.py: -------------------------------------------------------------------------------- 1 | import frida 2 | import time 3 | 4 | 5 | def init_frida(app, js_source): 6 | # setup Frida for the specified app 7 | js_payload = "" 8 | 9 | # read the javascript and store it in js_payload 10 | with open(js_source) as src: 11 | js_payload = src.read() 12 | 13 | # connect Frida to the emulator/android device 14 | device = frida.get_usb_device() 15 | 16 | # attach to the target 17 | session = device.attach(app) 18 | # inject payload 19 | script = session.create_script(js_payload) 20 | # return handler for the injected script 21 | return script 22 | 23 | 24 | def interactive_input(msg): 25 | 26 | response = input("Replace \"{}\": ".format(msg)) 27 | return response 28 | 29 | 30 | def on_message(message, data): 31 | print("Request received!") 32 | 33 | if message['type'] == u'send': 34 | time.sleep(1) 35 | user_content = interactive_input(message['payload']) 36 | response = {'type': u'input', u'payload': user_content} 37 | script.post(response) 38 | 39 | else: 40 | # Something went wrong 41 | print("Unexpected message received:") 42 | for key in message.keys(): 43 | print(key + ": " + str(message[key])) 44 | 45 | 46 | app = "space.polylog.owasp.needleremover" 47 | js_source = "text.js" 48 | script = init_frida(app, js_source) 49 | 50 | # register a function for handling message events with Frida 51 | script.on('message', on_message) 52 | # start 53 | script.load() 54 | 55 | # we do not want to exit 56 | while True: 57 | time.sleep(1) -------------------------------------------------------------------------------- /tasks/3/pinned_ssl.js: -------------------------------------------------------------------------------- 1 | if (Java.available){ 2 | Java.perform(function(){ 3 | var NetworkSecurityTrustManager = Java.use("android.security.net.config.NetworkSecurityTrustManager") 4 | 5 | NetworkSecurityTrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.net.Socket').implementation = function(a, b, c){ 6 | // see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/security/net/config/NetworkSecurityTrustManager.java 7 | // In case of succesfully validating the chain this method returns (return type is `void`). A failed validation throws a CertificateException. 8 | // Everything is fine by just returning, too. 9 | console.log("NetworkSecurityTrustManager.checkServerTrusted was ignored"); 10 | return; 11 | } 12 | 13 | NetworkSecurityTrustManager.checkPins.implementation = function(X509CertificateList){ 14 | // see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/security/net/config/NetworkSecurityTrustManager.java 15 | // In case of succesfully validating the chain this method returns (return type is `void`). A failed validation throws a CertificateException. 16 | // Everything is fine by just returning, too. 17 | console.log("NetworkSecurityTrustManager.checkServerTrusted just ignored"); 18 | 19 | return; 20 | }; 21 | 22 | }); 23 | } -------------------------------------------------------------------------------- /tasks/3/pinning.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pspace/bsidesmuc/baaf0c1e315e8c8c14e58512f219993ea577e52c/tasks/3/pinning.apk -------------------------------------------------------------------------------- /tasks/3/task3.txt: -------------------------------------------------------------------------------- 1 | Install 'pinning.apk' on the emulator. You have to install the old/unpinned version of the app first. 2 | Start the needleRemover app and try to see. 3 | 4 | Hint: You can use 'pidcat', or 'adb logcat' to figure out what you have to intercept. -------------------------------------------------------------------------------- /tasks/3/unpinned_ssl.js: -------------------------------------------------------------------------------- 1 | Java.perform(function(){ 2 | var ANDROID_VERSION_M = 23; 3 | 4 | var NetworkSecurityConfig = Java.use("android.security.net.config.NetworkSecurityConfig"); 5 | 6 | NetworkSecurityConfig.getDefaultBuilder.overload("int").implementation = function(targetSdkVersion){ 7 | console.log("[+] getDefaultBuilder original targetSdkVersion => " + targetSdkVersion.toString()); 8 | return this.getDefaultBuilder.overload("int").call(this, ANDROID_VERSION_M); 9 | }; 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /tasks/enumeration/enumerate_classes.js: -------------------------------------------------------------------------------- 1 | if(Java.available){ 2 | Java.perform(function() { 3 | Java.enumerateLoadedClasses({ 4 | onMatch: function(className) { 5 | send(className) 6 | }, 7 | onComplete: function() { 8 | send("DONE") 9 | } 10 | }); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /tasks/enumeration/enumerate_classes.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import frida 3 | import sys 4 | import time 5 | 6 | class_names = [] 7 | in_progress = True 8 | 9 | 10 | def init_frida(): 11 | js_payload = open("enumerate_classes.js").read() 12 | device = frida.get_usb_device() 13 | session = device.attach("space.polylog.owasp.needleremover") 14 | script = session.create_script(js_payload) 15 | return script 16 | 17 | 18 | def on_message(message, data): 19 | global in_progress 20 | global class_names 21 | 22 | if message['type'] == u'send': 23 | 24 | content = message['payload'] 25 | print(content) 26 | if not content == "DONE": 27 | class_names.append(content) 28 | else: 29 | output = open("classList.txt", 'w') 30 | output.write("\n".join(sorted(class_names))) 31 | in_progress = False 32 | else: 33 | print("Unexpected message received:") 34 | for key in message.keys(): 35 | print(key + ": " + str(message[key])) 36 | 37 | 38 | script = init_frida() 39 | script.on('message', on_message) 40 | script.load() 41 | 42 | while in_progress: 43 | time.sleep(1) 44 | sys.exit() 45 | -------------------------------------------------------------------------------- /tasks/funwithfrida/funwithfrida1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pspace/bsidesmuc/baaf0c1e315e8c8c14e58512f219993ea577e52c/tasks/funwithfrida/funwithfrida1.apk -------------------------------------------------------------------------------- /tasks/funwithfrida/funwithfrida2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pspace/bsidesmuc/baaf0c1e315e8c8c14e58512f219993ea577e52c/tasks/funwithfrida/funwithfrida2.apk -------------------------------------------------------------------------------- /tasks/needleRemover/pinning.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pspace/bsidesmuc/baaf0c1e315e8c8c14e58512f219993ea577e52c/tasks/needleRemover/pinning.apk -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/CounterActivity.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.TextView; 9 | 10 | import java.util.Timer; 11 | import java.util.TimerTask; 12 | 13 | import needleremover.R; 14 | 15 | 16 | public class CounterActivity extends AppCompatActivity { 17 | 18 | private int count; 19 | private boolean isCounting; 20 | private Timer timer; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_counter); 26 | isCounting = false; 27 | } 28 | 29 | @Override 30 | protected void onDestroy(){ 31 | super.onDestroy(); 32 | stopTimer(); 33 | } 34 | 35 | public synchronized void onClickStartStop(View view) { 36 | Log.i(this.getClass().getName(), "Start/Stop toggle pressed"); 37 | if (!isCounting) { 38 | startTimer(); 39 | } else { 40 | stopTimer(); 41 | } 42 | } 43 | 44 | private void startTimer() { 45 | Log.i(this.getClass().getName(), "Starting timer"); 46 | 47 | timer = new Timer(); 48 | TimerTask tt = getTimerTask(); 49 | timer.scheduleAtFixedRate(tt, 0l, 300l); 50 | isCounting = true; 51 | } 52 | 53 | private void stopTimer() { 54 | if(timer != null) { 55 | Log.i(this.getClass().getName(), "Stopping timer"); 56 | timer.cancel(); 57 | isCounting = false; 58 | timer = null; 59 | } 60 | } 61 | 62 | @NonNull 63 | private TimerTask getTimerTask() { 64 | return new TimerTask() { 65 | @Override 66 | public void run() { 67 | runOnUiThread( 68 | new Runnable() { 69 | @Override 70 | public void run() { 71 | int noToDisplay = getCount(); 72 | display(noToDisplay); 73 | incrementCount(); 74 | } 75 | } 76 | ); 77 | } 78 | }; 79 | } 80 | 81 | public void onClickReset(View view) { 82 | resetCounter(); 83 | } 84 | 85 | private int getCount() { 86 | return count; 87 | } 88 | 89 | private void incrementCount() { 90 | count++; 91 | } 92 | 93 | private void resetCounter() { 94 | count = 0; 95 | } 96 | 97 | private synchronized void display(Integer count) { 98 | Log.i(this.getClass().getName(), "Displaying: " + count.toString()); 99 | 100 | TextView counterView = (TextView) findViewById(R.id.counterView); 101 | counterView.setText(count.toString()); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/MainActivity.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.View; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | 14 | import needleremover.R; 15 | 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | private static final String TAG = "needleREmover"; 19 | 20 | 21 | // Used to load the 'native-lib' library on application startup. 22 | static { 23 | System.loadLibrary("native-lib"); 24 | } 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 31 | setSupportActionBar(toolbar); 32 | 33 | } 34 | 35 | @Override 36 | public boolean onCreateOptionsMenu(Menu menu) { 37 | // Inflate the menu; this adds items to the action bar if it is present. 38 | getMenuInflater().inflate(R.menu.menu_main, menu); 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean onOptionsItemSelected(MenuItem item) { 44 | // Handle action bar item clicks here. The action bar will 45 | // automatically handle clicks on the Home/Up button, so long 46 | // as you specify a parent activity in AndroidManifest.xml. 47 | int id = item.getItemId(); 48 | 49 | //noinspection SimplifiableIfStatement 50 | if (id == R.id.action_settings) { 51 | return true; 52 | } 53 | 54 | return super.onOptionsItemSelected(item); 55 | } 56 | 57 | 58 | 59 | public void onTextClick(View view) { 60 | Intent intent = new Intent(this, TextActivity.class); 61 | startActivity(intent); 62 | } 63 | 64 | public void onSSLClick(View view) { 65 | Intent intent = new Intent(this, SSLActivity.class); 66 | startActivity(intent); 67 | } 68 | 69 | public void onCounterClick(View view) { 70 | Intent intent = new Intent(this, CounterActivity.class); 71 | startActivity(intent); 72 | } 73 | 74 | public void onNativeClick(View view) { 75 | Intent intent = new Intent(this, Native_Call.class); 76 | startActivity(intent); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/Native_Call.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import needleremover.R; 9 | 10 | public class Native_Call extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_native__call); 16 | } 17 | 18 | /** 19 | * A native method that is implemented by the 'native-lib' native library, 20 | * which is packaged with this application. 21 | */ 22 | public native String stringFromJNI(); 23 | 24 | public void doNativeThing(View view) { 25 | TextView tv = (TextView) findViewById(R.id.nativeView); 26 | tv.setText(stringFromJNI()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/SSLActivity.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.text.Editable; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.widget.EditText; 12 | import android.widget.TextView; 13 | 14 | import java.security.cert.Certificate; 15 | import java.security.cert.X509Certificate; 16 | 17 | import needleremover.R; 18 | import space.polylog.owasp.needleremover.networking.NetworkFragment; 19 | import space.polylog.owasp.needleremover.networking.TLSDownloadCallback; 20 | 21 | 22 | public class SSLActivity extends AppCompatActivity implements TLSDownloadCallback { 23 | private static final String TAG = "needleREmover.TLS"; 24 | 25 | // Keep a reference to the NetworkFragment, which owns the AsyncTask object 26 | // that is used to execute network ops. 27 | private NetworkFragment mNetworkFragment; 28 | 29 | // Boolean telling us whether a download is in progress, so we don't trigger overlapping 30 | // downloads with consecutive button clicks. 31 | private boolean mDownloading = false; 32 | 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_ssl); 38 | mNetworkFragment = NetworkFragment.getInstance(getSupportFragmentManager()); 39 | 40 | } 41 | 42 | public void onGetClick(View view) { 43 | EditText urlView = (EditText) findViewById(R.id.url); 44 | Editable urlViewText = urlView.getText(); 45 | downloadURL(urlViewText.toString()); 46 | } 47 | 48 | private void downloadURL(String address) { 49 | 50 | if (!mDownloading && mNetworkFragment != null) { 51 | // Execute the async download. 52 | mNetworkFragment.startDownload(address); 53 | mDownloading = true; 54 | } 55 | 56 | } 57 | 58 | @Override 59 | public void updateFromDownload(String result) { 60 | TextView downloadContentView = (TextView) findViewById(R.id.contentView); 61 | downloadContentView.setText(result); 62 | } 63 | 64 | @Override 65 | public NetworkInfo getActiveNetworkInfo() { 66 | ConnectivityManager connectivityManager = 67 | (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 68 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 69 | return networkInfo; 70 | } 71 | 72 | @Override 73 | public void onProgressUpdate(Progress progressCode, int percentComplete) { 74 | Log.d(TAG, progressCode.name()); 75 | } 76 | 77 | @Override 78 | public void finishDownloading() { 79 | mDownloading = false; 80 | if (mNetworkFragment != null) { 81 | mNetworkFragment.cancelDownload(); 82 | } 83 | } 84 | 85 | @Override 86 | public void updateCertificateInfo(Certificate[] certificates) { 87 | 88 | StringBuilder infoBuilder = new StringBuilder(); 89 | infoBuilder.append("Certificates:\n"); 90 | if (certificates != null) { 91 | for (Certificate cert : certificates) { 92 | infoBuilder.append(String.format("Certificate %s:\n", cert.getType())); 93 | 94 | if ("X.509".equals(cert.getType())) { 95 | X509Certificate x509Certificate = (X509Certificate) cert; 96 | infoBuilder.append(String.format("DN name: %s\n", x509Certificate.getIssuerDN().getName())); 97 | infoBuilder.append(String.format("Serial number: %s\n", x509Certificate.getSerialNumber().toString(16))); 98 | } 99 | infoBuilder.append("\n"); 100 | } 101 | } else { 102 | infoBuilder.append("No certificates reported!"); 103 | } 104 | 105 | Log.d(TAG, infoBuilder.toString()); 106 | TextView downloadContentView = (TextView) findViewById(R.id.certificateView); 107 | downloadContentView.setText(infoBuilder.toString()); 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/TextActivity.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.EditText; 7 | import android.widget.TextView; 8 | 9 | import needleremover.R; 10 | 11 | public class TextActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_text); 17 | } 18 | 19 | private void setPanelContent(String new_content){ 20 | TextView simpleTextView = (TextView)findViewById(R.id.textView); 21 | simpleTextView.setText(new_content); 22 | } 23 | 24 | public void setTextOnClick(View view) { 25 | EditText editField = (EditText) findViewById(R.id.editText); 26 | setPanelContent(editField.getText().toString()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/networking/NetworkFragment.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover.networking; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | import android.os.AsyncTask; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v4.app.FragmentManager; 11 | import android.util.Log; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.InputStreamReader; 16 | import java.io.Reader; 17 | import java.io.UnsupportedEncodingException; 18 | import java.net.URL; 19 | import java.security.cert.Certificate; 20 | import java.security.cert.X509Certificate; 21 | 22 | import javax.net.ssl.HttpsURLConnection; 23 | 24 | /** 25 | * Created by hsp on 2/20/18. 26 | */ 27 | 28 | public class NetworkFragment extends Fragment { 29 | public static final String TAG = "NetworkFragment"; 30 | 31 | private static final String URL_KEY = "UrlKey"; 32 | 33 | private TLSDownloadCallback mCallback; 34 | private DownloadTask mDownloadTask; 35 | private Certificate[] certificates; 36 | 37 | /** 38 | * Static initializer for NetworkFragment that sets the URL of the host it will be downloading 39 | * from. 40 | */ 41 | public static NetworkFragment getInstance(FragmentManager fragmentManager) { 42 | // Recover NetworkFragment in case we are re-creating the Activity due to a config change. 43 | // This is necessary because NetworkFragment might have a task that began running before 44 | // the config change occurred and has not finished yet. 45 | // The NetworkFragment is recoverable because it calls setRetainInstance(true). 46 | NetworkFragment networkFragment = (NetworkFragment) fragmentManager 47 | .findFragmentByTag(NetworkFragment.TAG); 48 | if (networkFragment == null) { 49 | networkFragment = new NetworkFragment(); 50 | Bundle args = new Bundle(); 51 | networkFragment.setArguments(args); 52 | fragmentManager.beginTransaction().add(networkFragment, TAG).commit(); 53 | } 54 | return networkFragment; 55 | } 56 | 57 | @Override 58 | public void onCreate(@Nullable Bundle savedInstanceState) { 59 | super.onCreate(savedInstanceState); 60 | // Retain this Fragment across configuration changes in the host Activity. 61 | setRetainInstance(true); 62 | } 63 | 64 | @Override 65 | public void onAttach(Context context) { 66 | super.onAttach(context); 67 | // Host Activity will handle callbacks from task. 68 | mCallback = (TLSDownloadCallback) context; 69 | } 70 | 71 | @Override 72 | public void onDetach() { 73 | super.onDetach(); 74 | // Clear reference to host Activity to avoid memory leak. 75 | mCallback = null; 76 | } 77 | 78 | @Override 79 | public void onDestroy() { 80 | // Cancel task when Fragment is destroyed. 81 | cancelDownload(); 82 | super.onDestroy(); 83 | } 84 | 85 | /** 86 | * Start non-blocking execution of DownloadTask. 87 | */ 88 | public void startDownload(String url) { 89 | cancelDownload(); 90 | mDownloadTask = new DownloadTask(mCallback); 91 | mDownloadTask.execute(url); 92 | } 93 | 94 | /** 95 | * Cancel (and interrupt if necessary) any ongoing DownloadTask execution. 96 | */ 97 | public void cancelDownload() { 98 | if (mDownloadTask != null) { 99 | mDownloadTask.cancel(true); 100 | } 101 | } 102 | 103 | 104 | /** 105 | * Implementation of AsyncTask designed to fetch data from the network. 106 | */ 107 | private class DownloadTask extends AsyncTask { 108 | 109 | private TLSDownloadCallback mCallback; 110 | 111 | DownloadTask(TLSDownloadCallback callback) { 112 | setCallback(callback); 113 | } 114 | 115 | void setCallback(TLSDownloadCallback callback) { 116 | mCallback = callback; 117 | } 118 | 119 | /** 120 | * Wrapper class that serves as a union of a result value and an exception. When the download 121 | * task has completed, either the result value or exception can be a non-null value. 122 | * This allows you to pass exceptions to the UI thread that were thrown during doInBackground(). 123 | */ 124 | class Result { 125 | public String mResultValue; 126 | public Exception mException; 127 | 128 | public Result(String resultValue) { 129 | mResultValue = resultValue; 130 | mException = null; 131 | } 132 | 133 | public Result(Exception exception) { 134 | mException = exception; 135 | mResultValue = null; 136 | } 137 | 138 | public boolean hasResult(){ 139 | return mResultValue != null; 140 | } 141 | 142 | public boolean hasError(){ 143 | return mException != null; 144 | } 145 | 146 | 147 | } 148 | 149 | /** 150 | * Cancel background network operation if we do not have network connectivity. 151 | */ 152 | @Override 153 | protected void onPreExecute() { 154 | if (mCallback != null) { 155 | NetworkInfo networkInfo = mCallback.getActiveNetworkInfo(); 156 | if (networkInfo == null || !networkInfo.isConnected() || 157 | (networkInfo.getType() != ConnectivityManager.TYPE_WIFI 158 | && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) { 159 | // If no connectivity, cancel task and update Callback with null data. 160 | mCallback.updateFromDownload(null); 161 | cancel(true); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * Defines work to perform on the background thread. 168 | */ 169 | @Override 170 | protected DownloadTask.Result doInBackground(String... urls) { 171 | Result result = null; 172 | if (!isCancelled() && urls != null && urls.length > 0) { 173 | String urlString = urls[0]; 174 | try { 175 | URL url = new URL(urlString); 176 | String resultString = downloadUrl(url); 177 | if (resultString != null) { 178 | result = new Result(resultString); 179 | } else { 180 | throw new IOException("No response received."); 181 | } 182 | } catch (Exception e) { 183 | result = new Result(e); 184 | } 185 | } 186 | return result; 187 | } 188 | 189 | /** 190 | * Updates the DownloadCallback with the result. 191 | */ 192 | @Override 193 | protected void onPostExecute(Result result) { 194 | if (result != null && mCallback != null) { 195 | if (result.mException != null) { 196 | mCallback.updateFromDownload(result.mException.getMessage()); 197 | } else if (result.mResultValue != null) { 198 | mCallback.updateFromDownload(result.mResultValue); 199 | } 200 | mCallback.updateCertificateInfo(certificates); 201 | mCallback.finishDownloading(); 202 | } 203 | } 204 | 205 | /** 206 | * Override to add special behavior for cancelled AsyncTask. 207 | */ 208 | @Override 209 | protected void onCancelled(Result result) { 210 | } 211 | 212 | private String compileCertInfo(Certificate[] certificates){ 213 | StringBuilder infoBuilder = new StringBuilder(); 214 | infoBuilder.append("Certificates: %s\n"); 215 | 216 | for (Certificate cert : certificates){ 217 | infoBuilder.append(String.format("Certificate: %s\n", cert.getType())); 218 | 219 | if("X.509".equals(cert.getType())){ 220 | X509Certificate x509Certificate = (X509Certificate) cert; 221 | infoBuilder.append(String.format("DN name: %s\n", x509Certificate.getIssuerDN().getName())); 222 | infoBuilder.append(String.format("Serial number: %s\n", x509Certificate.getSerialNumber().toString(16))); 223 | } 224 | infoBuilder.append("\n"); 225 | } 226 | 227 | Log.d(TAG, infoBuilder.toString()); 228 | return infoBuilder.toString(); 229 | } 230 | 231 | /** 232 | * Given a URL, sets up a connection and gets the HTTP response body from the server. 233 | * If the network request is successful, it returns the response body in String form. Otherwise, 234 | * it will throw an IOException. 235 | */ 236 | private String downloadUrl(URL url) throws IOException { 237 | InputStream stream = null; 238 | HttpsURLConnection connection = null; 239 | String result = null; 240 | try { 241 | connection = (HttpsURLConnection) url.openConnection(); 242 | // Timeout for reading InputStream arbitrarily set to 3000ms. 243 | connection.setReadTimeout(3000); 244 | // Timeout for connection.connect() arbitrarily set to 3000ms. 245 | connection.setConnectTimeout(3000); 246 | // For this use case, set HTTP method to GET. 247 | connection.setRequestMethod("GET"); 248 | // Already true by default but setting just in case; needs to be true since this request 249 | // is carrying an input (response) body. 250 | connection.setDoInput(true); 251 | // Open communications link (network traffic occurs here). 252 | connection.connect(); 253 | 254 | publishProgress(TLSDownloadCallback.Progress.CONNECT_SUCCESS.ordinal()); 255 | 256 | int responseCode = connection.getResponseCode(); 257 | if (responseCode != HttpsURLConnection.HTTP_OK) { 258 | throw new IOException("HTTP error code: " + responseCode); 259 | } 260 | // Retrieve the response body as an InputStream. 261 | stream = connection.getInputStream(); 262 | publishProgress(TLSDownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS.ordinal(), 0); 263 | if (stream != null) { 264 | // Converts Stream to String with max length of 500. 265 | result = readStream(stream, 500); 266 | } 267 | } catch(Exception e){ 268 | Log.e(TAG, e.getMessage(),e); 269 | } 270 | finally { 271 | // Close Stream and disconnect HTTPS connection. 272 | if (stream != null) { 273 | stream.close(); 274 | } 275 | if (connection != null) { 276 | Certificate[] serverCertificates = connection.getServerCertificates(); 277 | certificates = serverCertificates; 278 | connection.disconnect(); 279 | } 280 | } 281 | return result; 282 | } 283 | 284 | /** 285 | * Converts the contents of an InputStream to a String. 286 | */ 287 | public String readStream(InputStream stream, int maxReadSize) 288 | throws IOException, UnsupportedEncodingException { 289 | Reader reader = null; 290 | reader = new InputStreamReader(stream, "UTF-8"); 291 | char[] rawBuffer = new char[maxReadSize]; 292 | int readSize; 293 | StringBuffer buffer = new StringBuffer(); 294 | while (((readSize = reader.read(rawBuffer)) != -1) && maxReadSize > 0) { 295 | if (readSize > maxReadSize) { 296 | readSize = maxReadSize; 297 | } 298 | buffer.append(rawBuffer, 0, readSize); 299 | maxReadSize -= readSize; 300 | } 301 | return buffer.toString(); 302 | } 303 | } 304 | 305 | } -------------------------------------------------------------------------------- /tasks/needleRemover/src/space/polylog/owasp/needleremover/networking/TLSDownloadCallback.java: -------------------------------------------------------------------------------- 1 | package space.polylog.owasp.needleremover.networking; 2 | 3 | import android.net.NetworkInfo; 4 | 5 | import java.security.cert.Certificate; 6 | 7 | public interface TLSDownloadCallback { 8 | enum Progress { 9 | ERROR(-1), 10 | CONNECT_SUCCESS(0), 11 | GET_INPUT_STREAM_SUCCESS(1), 12 | PROCESS_INPUT_STREAM_IN_PROGRESS(2), 13 | PROCESS_INPUT_STREAM_SUCCESS(3); 14 | 15 | private int status; 16 | Progress(int i){ 17 | status = i; 18 | } 19 | } 20 | 21 | /** 22 | * Indicates that the callback handler needs to update its appearance or information based on 23 | * the result of the task. Expected to be called from the main thread. 24 | */ 25 | void updateFromDownload(String result); 26 | 27 | /** 28 | * Get the device's active network status in the form of a NetworkInfo object. 29 | */ 30 | NetworkInfo getActiveNetworkInfo(); 31 | 32 | /** 33 | * Indicate to callback handler any progress update. 34 | * @param progress must be one of the constants defined in DownloadCallback.Progress. 35 | * @param percentComplete must be 0-100. 36 | */ 37 | void onProgressUpdate(Progress progress, int percentComplete); 38 | 39 | /** 40 | * Indicates that the download operation has finished. This method is called even if the 41 | * download hasn't completed successfully. 42 | */ 43 | void finishDownloading(); 44 | 45 | void updateCertificateInfo(Certificate[] certificates); 46 | } 47 | -------------------------------------------------------------------------------- /unpack-apk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #https://koz.io/using-frida-on-android-without-root/ 3 | CWD=`pwd` 4 | 5 | APKFILE='invalid' 6 | 7 | DOCSTRING="Usage: ./unpack-apk.sh -d -f APKFILE" 8 | while [ "$1" != "" ]; 9 | do 10 | case $1 in 11 | -f |--file ) 12 | shift 13 | APKFILE="$1" 14 | ;; 15 | -d ) 16 | DECOMPILE=1 17 | ;; 18 | -h|--help) 19 | echo $DOCSTRING 20 | exit 21 | ;; 22 | *) 23 | echo $1 24 | echo $DOCSTRING 25 | exit 26 | ;; 27 | esac 28 | shift 29 | done 30 | 31 | 32 | if [ ! -f $APKFILE ]; then 33 | echo "APK not found! Exiting!" 34 | exit -1 35 | fi 36 | 37 | PROJECT_DIR="${APKFILE%.*}" 38 | EXTRACT_DIR="$PROJECT_DIR/extracted" 39 | mkdir -p "$PROJECT_DIR" 40 | 41 | APKBASENAME=`basename "$APKNAME"` 42 | 43 | echo "Copying $APKFILE to $PROJECT_DIR" 44 | cp "$APKFILE" "$PROJECT_DIR/$APKBASENAME" 45 | 46 | 47 | if [ -d "$EXTRACT_DIR" ]; then rm -Rf "$EXTRACT_DIR"; fi 48 | 49 | echo "Unpacking $APKFILE to $PROJECT_DIR/$APKBASENAME" 50 | apktool d -f -o "$EXTRACT_DIR" $APKFILE 51 | 52 | if [ $DECOMPILE ]; then 53 | echo "Decompile $APKFILE to $PROJECT_DIR/$APKBASENAME" 54 | jadx --export-gradle -d $EXTRACT_DIR $APKFILE 55 | fi 56 | -------------------------------------------------------------------------------- /update-frida.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CWD=`pwd` 3 | 4 | # create directory for binary content 5 | mkdir -p frida-libs 6 | cd frida-libs 7 | 8 | # Get the latest version number 9 | tmpfile=tmp.json 10 | curl -s -X GET https://api.github.com/repos/frida/frida/tags -o $tmpfile 11 | LATEST_RELEASE=`cat $tmpfile |grep name | head -1 | sed 's/\"//g' | sed 's/\,//g'| gawk '{split($0,array,": ")} END{print array[2]}'` 12 | rm $tmpfile 13 | 14 | # Store it for the deplyment script to find the correct version 15 | echo Updating frida to $LATEST_RELEASE 16 | echo $LATEST_RELEASE > "LATEST_RELEASE" 17 | 18 | # This depends on your preferences and how your distro/operating system names the pip binary 19 | # Windows users will have to adjust this (if you use Pycharm you should be able to install Frida via Pycharms built-in package manager). 20 | sudo pip3 install frida-tools --upgrade 21 | sudo pip2 install frida-tools --upgrade 22 | 23 | 24 | # we are inside frida-libs - clean up older versions 25 | rm -f frida* 26 | 27 | # download x86/x86_64/arm/arm64 server and gadget files - just add other architectures here 28 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-gadget-$LATEST_RELEASE-android-x86_64.so.xz 29 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-gadget-$LATEST_RELEASE-android-x86.so.xz 30 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-gadget-$LATEST_RELEASE-android-arm64.so.xz 31 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-gadget-$LATEST_RELEASE-android-arm.so.xz 32 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-server-$LATEST_RELEASE-android-x86_64.xz 33 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-server-$LATEST_RELEASE-android-x86.xz 34 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-server-$LATEST_RELEASE-android-arm64.xz 35 | curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-server-$LATEST_RELEASE-android-arm.xz 36 | # curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-gadget-$LATEST_RELEASE-linux-x86_64.so.xz 37 | # curl -L -O -J https://github.com/frida/frida/releases/download/$LATEST_RELEASE/frida-server-$LATEST_RELEASE-linux-x86_64.xz 38 | 39 | 40 | # unpack the downloaded archives 41 | for f in *.xz 42 | do 43 | 7z x "$f" 44 | done 45 | rm *.xz 46 | 47 | # go back to where we started 48 | cd "$CWD" 49 | --------------------------------------------------------------------------------