diff --git a/build.gradle b/build.gradle index cd32465..e7a76f1 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,6 @@ task deleteLibs(type: Delete) { preBuild.dependsOn downloadx86*/ dependencies { - implementation libs.commons.codec implementation libs.appcompat implementation libs.material implementation libs.activity @@ -69,7 +68,7 @@ dependencies { implementation 'io.reactivex.rxjava3:rxjava:3.1.5' compileOnly 'org.projectlombok:lombok:1.18.34' annotationProcessor 'org.projectlombok:lombok:1.18.34' - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' + implementation libs.androidx.coordinatorlayout annotationProcessor libs.room.compiler testImplementation libs.junit androidTestImplementation libs.ext.junit diff --git a/src/main/java/com/alterdekim/frida/FridaLib.java b/src/main/java/com/alterdekim/frida/FridaLib.java index f07916f..f6b97d9 100644 --- a/src/main/java/com/alterdekim/frida/FridaLib.java +++ b/src/main/java/com/alterdekim/frida/FridaLib.java @@ -8,7 +8,7 @@ public class FridaLib { System.loadLibrary("frida"); } - public native int start(String config_b32, int tun_fd, boolean close_fd_on_drop); + public native int start(String config_hex, int tun_fd, boolean close_fd_on_drop); public native int stop(); diff --git a/src/main/java/com/alterdekim/fridaapp/activity/MainActivity.java b/src/main/java/com/alterdekim/fridaapp/activity/MainActivity.java index c1b765a..f423d7d 100644 --- a/src/main/java/com/alterdekim/fridaapp/activity/MainActivity.java +++ b/src/main/java/com/alterdekim/fridaapp/activity/MainActivity.java @@ -33,8 +33,6 @@ import com.alterdekim.fridaapp.room.Config; import com.alterdekim.fridaapp.service.FridaService; import com.google.android.material.switchmaterial.SwitchMaterial; -import org.apache.commons.codec.binary.Base32; - import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Iterator; @@ -57,8 +55,8 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI try { String raw_data = Util.readTextFromUri(this, data.getData()); String name = Util.getFilenameFromUri(this, data.getData()); - String b32 = Base32.builder().get().encodeToString(raw_data.getBytes(StandardCharsets.UTF_8)); - this.controller.insertNewConfig(name, b32); + String hex = Util.bytesToHex(raw_data.getBytes()); + this.controller.insertNewConfig(name, hex); } catch (IOException e) { Log.e(TAG, e.getMessage()); } @@ -68,7 +66,7 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() != RESULT_OK) return; - startService(new Intent(this, FridaService.class)); + startVpnService(); } ); @@ -95,6 +93,8 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI popup.getMenuInflater().inflate(R.menu.mm, popup.getMenu()); popup.show(); }); + + startVpn(); } @Override @@ -125,6 +125,10 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI launcher.launch(intent); return; } + startVpnService(); + } + + private void startVpnService() { startService(new Intent(this, FridaService.class)); } } \ No newline at end of file diff --git a/src/main/java/com/alterdekim/fridaapp/controller/MainActivityController.java b/src/main/java/com/alterdekim/fridaapp/controller/MainActivityController.java index a0b6c66..2d7842a 100644 --- a/src/main/java/com/alterdekim/fridaapp/controller/MainActivityController.java +++ b/src/main/java/com/alterdekim/fridaapp/controller/MainActivityController.java @@ -1,5 +1,6 @@ package com.alterdekim.fridaapp.controller; +import android.content.Intent; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -13,11 +14,12 @@ import com.alterdekim.fridaapp.R; import com.alterdekim.fridaapp.activity.MainActivity; import com.alterdekim.fridaapp.room.AppDatabase; import com.alterdekim.fridaapp.room.Config; +import com.alterdekim.fridaapp.service.FridaService; import com.google.android.material.switchmaterial.SwitchMaterial; +import java.util.Iterator; + import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.functions.Action; -import io.reactivex.rxjava3.functions.Consumer; import io.reactivex.rxjava3.schedulers.Schedulers; public class MainActivityController implements IController { @@ -41,30 +43,48 @@ public class MainActivityController implements IController { } private void initConfigListGUI() { + Toast.makeText(this.mainActivity, R.string.config_adding_success, Toast.LENGTH_LONG).show(); LayoutInflater inflater = this.mainActivity.getLayoutInflater(); - this.mainActivity.getCfg_list().removeAllViews(); this.db.userDao().getAll() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(config -> { - View cfg_instance = inflater.inflate(R.layout.single_config, this.mainActivity.getCfg_list(), false); - this.mainActivity.getCfg_list().addView(cfg_instance); - TextView view_name = (TextView) cfg_instance.findViewById(R.id.config_name); - SwitchMaterial view_switch = (SwitchMaterial) cfg_instance.findViewById(R.id.config_switch); - view_switch.setUseMaterialThemeColors(true); - view_switch.setOnCheckedChangeListener((compoundButton, b) -> Log.i(TAG, "onCheckedChanged " + b)); - view_name.setText(config.title); + .doOnNext(cl -> { + this.mainActivity.getCfg_list().removeAllViews(); + if( cl.isEmpty() ) { + this.mainActivity.getCfg_list().addView(inflater.inflate(R.layout.content_nocfg, this.mainActivity.getCfg_list(), false)); + return; + } + Iterator iter = cl.iterator(); + while( iter.hasNext() ) { + Config config = iter.next(); + View cfg_instance = inflater.inflate(R.layout.single_config, this.mainActivity.getCfg_list(), false); + this.mainActivity.getCfg_list().addView(cfg_instance); + TextView view_name = (TextView) cfg_instance.findViewById(R.id.config_name); + SwitchMaterial view_switch = (SwitchMaterial) cfg_instance.findViewById(R.id.config_switch); + view_switch.setUseMaterialThemeColors(true); + view_switch.setOnCheckedChangeListener((compoundButton, b) -> toggleVpn(view_switch, config, b)); + view_name.setText(config.getTitle()); + if( iter.hasNext() ) this.mainActivity.getCfg_list().addView(inflater.inflate(R.layout.single_divider, this.mainActivity.getCfg_list(), false)); + } }) - .doAfterNext(config -> inflater.inflate(R.layout.single_divider, this.mainActivity.getCfg_list(), false)) + .doOnError(throwable -> this.mainActivity.getCfg_list().addView(inflater.inflate(R.layout.content_error, this.mainActivity.getCfg_list(), false))) .subscribe(); } - public void insertNewConfig(String name, String b32) { - db.userDao().insertAll(new Config(name, b32)) + public void insertNewConfig(String name, String hex) { + db.userDao().insertAll(new Config(name, hex)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnError(throwable -> Toast.makeText(MainActivityController.this.mainActivity, R.string.config_adding_error, Toast.LENGTH_LONG).show()) .doOnComplete(this::initConfigListGUI) .subscribe(); } + + private void toggleVpn(SwitchMaterial view, Config config, boolean val) { + Intent intent = new Intent(this.mainActivity, FridaService.class); + intent.putExtra("vpn_hex", config.getData_hex()); + intent.putExtra("vpn_uid", config.getUid()); + intent.putExtra("vpn_state", val); + this.mainActivity.startService(intent); + } } diff --git a/src/main/java/com/alterdekim/fridaapp/room/Config.java b/src/main/java/com/alterdekim/fridaapp/room/Config.java index de0161b..83f0027 100644 --- a/src/main/java/com/alterdekim/fridaapp/room/Config.java +++ b/src/main/java/com/alterdekim/fridaapp/room/Config.java @@ -7,18 +7,20 @@ import androidx.room.PrimaryKey; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import lombok.ToString; +@ToString @Entity @RequiredArgsConstructor @Getter @Setter public class Config { @PrimaryKey(autoGenerate = true) - public int uid; + private int uid; @ColumnInfo(name = "title") - public final String title; + private final String title; - @ColumnInfo(name = "data_b32") - public final String data_b32; + @ColumnInfo(name = "data_hex") + private final String data_hex; } diff --git a/src/main/java/com/alterdekim/fridaapp/room/ConfigDAO.java b/src/main/java/com/alterdekim/fridaapp/room/ConfigDAO.java index 6d87d65..43e9442 100644 --- a/src/main/java/com/alterdekim/fridaapp/room/ConfigDAO.java +++ b/src/main/java/com/alterdekim/fridaapp/room/ConfigDAO.java @@ -3,24 +3,28 @@ package com.alterdekim.fridaapp.room; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; +import androidx.room.OnConflictStrategy; import androidx.room.Query; +import java.util.List; + import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Single; @Dao public interface ConfigDAO { @Query("SELECT * FROM config") - Observable getAll(); + Flowable> getAll(); @Query("SELECT * FROM config WHERE uid IN (:cfgIds)") - Observable loadAllByIds(int[] cfgIds); + Flowable> loadAllByIds(int[] cfgIds); @Query("SELECT * FROM config WHERE uid = :cfgId") Single loadById(int cfgId); - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) Completable insertAll(Config... users); @Delete diff --git a/src/main/java/com/alterdekim/fridaapp/service/FridaService.java b/src/main/java/com/alterdekim/fridaapp/service/FridaService.java index 41d6ef0..e87ef48 100644 --- a/src/main/java/com/alterdekim/fridaapp/service/FridaService.java +++ b/src/main/java/com/alterdekim/fridaapp/service/FridaService.java @@ -6,24 +6,28 @@ import android.net.VpnService; import android.os.ParcelFileDescriptor; import android.util.Log; +import java.util.Objects; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.functions.Consumer; +import io.reactivex.rxjava3.schedulers.Schedulers; +import io.reactivex.rxjava3.subjects.BehaviorSubject; + public class FridaService extends VpnService { private static final String TAG = FridaService.class.getSimpleName(); private static final String VPN_ADDRESS = "10.66.66.6"; // Only IPv4 support for now private static final String VPN_ROUTE = "0.0.0.0"; // Intercept everything private ParcelFileDescriptor vpnInterface = null; - private PendingIntent pendingIntent; + + private Disposable vpnProcess; @Override public void onCreate() { + Log.i(TAG, "Created"); setupVPN(); - Log.i(TAG, "Started"); - try { - Thread t = new Thread(new NativeBinaryConnection(vpnInterface.dup().detachFd(), getApplicationContext().getApplicationInfo().nativeLibraryDir)); - t.start(); - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - } } private void setupVPN() { @@ -41,8 +45,33 @@ public class FridaService extends VpnService { } } + @Override + public void onDestroy() { + turnOff(); + } + + private void turnOff() { + if( this.vpnProcess != null && !this.vpnProcess.isDisposed() ) this.vpnProcess.dispose(); + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { + if( intent.getExtras() == null ) return START_STICKY; + String hex = intent.getExtras().getString("vpn_hex"); + int uid = intent.getExtras().getInt("vpn_uid"); + boolean state = intent.getExtras().getBoolean("vpn_state"); + turnOff(); + if(!state) return START_STICKY; + // TODO: different configs + /*this.vpnProcess = Flowable.fromRunnable(new NativeBinaryConnection(vpnInterface.detachFd(), hex)) + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe();*/ + try { + Thread t = new Thread(new NativeBinaryConnection(vpnInterface.dup().detachFd(), hex)); + t.start(); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } return START_STICKY; } } diff --git a/src/main/java/com/alterdekim/fridaapp/service/NativeBinaryConnection.java b/src/main/java/com/alterdekim/fridaapp/service/NativeBinaryConnection.java index e5881a5..2d57ab5 100644 --- a/src/main/java/com/alterdekim/fridaapp/service/NativeBinaryConnection.java +++ b/src/main/java/com/alterdekim/fridaapp/service/NativeBinaryConnection.java @@ -4,16 +4,14 @@ import android.util.Log; import com.alterdekim.frida.FridaLib; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor public class NativeBinaryConnection implements Runnable { private static final String TAG = NativeBinaryConnection.class.getSimpleName(); - private int fd = 0; - private String baseDir; - - public NativeBinaryConnection(int fd, String baseDir) { - this.fd = fd; - this.baseDir = baseDir; - } + private final int fd; + private final String hex; @Override public void run() { @@ -21,7 +19,8 @@ public class NativeBinaryConnection implements Runnable { Log.i(TAG, "FD: " + this.fd); FridaLib lib = new FridaLib(); Log.i(TAG, "Starting Frida client"); - int r = lib.start("", this.fd, false); + Log.i(TAG, "Hex: " + this.hex); + int r = lib.start(this.hex.toLowerCase(), this.fd, false); Log.i(TAG, "Exit code: " + r); } catch (Exception e) { Log.e(TAG, e.getMessage()); diff --git a/src/main/java/com/alterdekim/fridaapp/util/Util.java b/src/main/java/com/alterdekim/fridaapp/util/Util.java index 851f177..2a2f22e 100644 --- a/src/main/java/com/alterdekim/fridaapp/util/Util.java +++ b/src/main/java/com/alterdekim/fridaapp/util/Util.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.provider.OpenableColumns; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -13,17 +14,15 @@ import java.util.Objects; public class Util { public static String readTextFromUri(Context context, Uri uri) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); try (InputStream inputStream = - context.getContentResolver().openInputStream(uri); - BufferedReader reader = new BufferedReader( - new InputStreamReader(Objects.requireNonNull(inputStream)))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); + context.getContentResolver().openInputStream(uri)) { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + for (int length; (length = inputStream.read(buffer)) != -1; ) { + result.write(buffer, 0, length); } + return result.toString(); } - return stringBuilder.toString(); } public static String getFilenameFromUri(Context context, Uri uri) { @@ -34,4 +33,15 @@ public class Util { } return "default"; } + + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } } diff --git a/src/main/res/drawable/baseline_error_24.xml b/src/main/res/drawable/baseline_error_24.xml new file mode 100644 index 0000000..0532a0b --- /dev/null +++ b/src/main/res/drawable/baseline_error_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/res/drawable/baseline_keyboard_command_key_24.xml b/src/main/res/drawable/baseline_keyboard_command_key_24.xml new file mode 100644 index 0000000..4d5648e --- /dev/null +++ b/src/main/res/drawable/baseline_keyboard_command_key_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/res/layout/activity_main.xml b/src/main/res/layout/activity_main.xml index 2a78eb9..889c838 100644 --- a/src/main/res/layout/activity_main.xml +++ b/src/main/res/layout/activity_main.xml @@ -1,52 +1,37 @@ - - + android:layout_height="wrap_content" + android:theme="@style/Widget.Material3.AppBarLayout"> - - - - - - + android:layout_height="?attr/actionBarSize" + app:popupTheme="@style/Widget.Material3.AppBarLayout" /> - - + - + - + - - \ No newline at end of file + \ No newline at end of file diff --git a/src/main/res/layout/content_error.xml b/src/main/res/layout/content_error.xml new file mode 100644 index 0000000..229145f --- /dev/null +++ b/src/main/res/layout/content_error.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/content_main.xml b/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..72d8ef9 --- /dev/null +++ b/src/main/res/layout/content_main.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/src/main/res/layout/content_nocfg.xml b/src/main/res/layout/content_nocfg.xml new file mode 100644 index 0000000..2536a4b --- /dev/null +++ b/src/main/res/layout/content_nocfg.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a85e04d..6e7bde4 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -3,4 +3,5 @@ Create from scratch Import from file Cannot add new config + SUCCESS! \ No newline at end of file