├── .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 |
--------------------------------------------------------------------------------