|
@@ -1,104 +1,230 @@
|
|
|
package de.tudarmstadt.informatik.hostage.system;
|
|
|
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileNotFoundException;
|
|
|
+import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.io.OutputStream;
|
|
|
|
|
|
import android.app.Activity;
|
|
|
-import android.content.Intent;
|
|
|
-import android.content.SharedPreferences;
|
|
|
-import android.preference.PreferenceManager;
|
|
|
import android.util.Log;
|
|
|
|
|
|
-import de.tudarmstadt.informatik.hostage.Hostage;
|
|
|
-import de.tudarmstadt.informatik.hostage.R;
|
|
|
+import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
|
|
|
+import de.tudarmstadt.informatik.hostage.ui.activity.MainActivity;
|
|
|
|
|
|
|
|
|
public class Device {
|
|
|
-
|
|
|
- @SuppressWarnings("unused")
|
|
|
- // DO NOT REMOVE, NEEDED FOR SINGLETON INITIALIZATION
|
|
|
- private static final Device INSTANCE = new Device();
|
|
|
-
|
|
|
+ private static String porthackFilepath = "/data/local/bind";
|
|
|
private static boolean initialized = false;
|
|
|
- private static boolean root = false;
|
|
|
- private static boolean pp = false;
|
|
|
-
|
|
|
- private Device() {
|
|
|
-
|
|
|
- new Thread(new Runnable() {
|
|
|
- @Override
|
|
|
- public void run() {
|
|
|
- try {
|
|
|
- String portBinder = "[ -e /data/local/bind ]";
|
|
|
-
|
|
|
- Process su = new ProcessBuilder("su", "-c", portBinder).start();
|
|
|
- switch (su.waitFor()) {
|
|
|
- case 0:
|
|
|
- root = true;
|
|
|
- pp = true;
|
|
|
- break;
|
|
|
- case 1:
|
|
|
- root = true;
|
|
|
- pp = false;
|
|
|
- break;
|
|
|
- case 127:
|
|
|
- root = false;
|
|
|
- pp = false;
|
|
|
- break;
|
|
|
- }
|
|
|
- } catch (InterruptedException e) {
|
|
|
- } catch (IOException e) {
|
|
|
- } finally {
|
|
|
- initialized = true;
|
|
|
- Log.d("hostage", "Root: " + Boolean.toString(root));
|
|
|
- Log.d("hostage", "PP: " + Boolean.toString(pp));
|
|
|
- }
|
|
|
+ private static boolean root = false; // device is rooted
|
|
|
+ private static boolean porthack = false; // porthack installed
|
|
|
+ private static boolean iptables = false; // iptables redirection confirmed working
|
|
|
+
|
|
|
+ public static void checkCapabilities() {
|
|
|
+ // assume worst case
|
|
|
+ initialized = false;
|
|
|
+ root = false;
|
|
|
+ porthack = false;
|
|
|
+ iptables = false;
|
|
|
+
|
|
|
+ porthackFilepath = getPorthackFilepath();
|
|
|
+ String porthackExists = "[ -e "+porthackFilepath+" ]"; // checks existence of porthack
|
|
|
+
|
|
|
+ try {
|
|
|
+ Process p = new ProcessBuilder("su", "-c", porthackExists).start();
|
|
|
+ switch (p.waitFor()) {
|
|
|
+ case 0: porthack = true;
|
|
|
+ // fall through and don't break
|
|
|
+ case 1: root = true; // 0 and 1 are valid return values of the porthack
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 127: // command not found or executable
|
|
|
+ root = false;
|
|
|
+ porthack = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ final String ipTablesList = "iptables -L -n -t nat"; // list all rules in NAT table
|
|
|
+ try {
|
|
|
+ Process p = new ProcessBuilder("su", "-c", ipTablesList).start();
|
|
|
+ switch (p.waitFor()) {
|
|
|
+ case 0: // everything is fine
|
|
|
+ iptables = true; // iptables available and working
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 3: // no such table
|
|
|
+ case 127: // command not found
|
|
|
+ default: // unexpected return code
|
|
|
+ // while testing code 3 has been returned when table NAT is not available
|
|
|
+ iptables = false;
|
|
|
}
|
|
|
- }).start();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ initialized = true;
|
|
|
}
|
|
|
|
|
|
public static boolean isRooted() {
|
|
|
- while (!initialized)
|
|
|
- ;
|
|
|
+ assert(initialized);
|
|
|
return root;
|
|
|
}
|
|
|
|
|
|
public static boolean isPorthackInstalled() {
|
|
|
- while (!initialized)
|
|
|
- ;
|
|
|
- return pp;
|
|
|
+ assert(initialized);
|
|
|
+ return porthack;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Called after auto-loading porthack. To update the local variables.
|
|
|
- */
|
|
|
-
|
|
|
- public static boolean updatePorthack (){
|
|
|
- try {
|
|
|
- String portBinder = "[ -e /data/local/bind ]";
|
|
|
-
|
|
|
- Process su = new ProcessBuilder("su", "-c", portBinder).start();
|
|
|
- switch (su.waitFor()) {
|
|
|
- case 0:
|
|
|
- root = true;
|
|
|
- pp = true;
|
|
|
- break;
|
|
|
- case 1:
|
|
|
- root = true;
|
|
|
- pp = false;
|
|
|
- break;
|
|
|
- case 127:
|
|
|
- root = false;
|
|
|
- pp = false;
|
|
|
- break;
|
|
|
- }
|
|
|
- } catch (InterruptedException e) {
|
|
|
- } catch (IOException e) {
|
|
|
- } finally {
|
|
|
- initialized = true;
|
|
|
- Log.d("hostage", "Root: " + Boolean.toString(root));
|
|
|
- Log.d("hostage", "PP: " + Boolean.toString(pp));
|
|
|
- }
|
|
|
- return (pp && root);
|
|
|
- }
|
|
|
+ public static boolean isPortRedirectionAvailable() { // using iptables
|
|
|
+ assert(initialized);
|
|
|
+ return iptables;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * copies an asset to the local filesystem for later usage
|
|
|
+ * (used for port hack and shell scripts)
|
|
|
+ * @param assetFilePath
|
|
|
+ * @param destFilePath
|
|
|
+ * @return true on success
|
|
|
+ */
|
|
|
+ private static boolean deployAsset(String assetFilePath, String destFilePath) {
|
|
|
+ Activity activity = MainActivity.getInstance();
|
|
|
+ File file = new File(activity.getFilesDir(), destFilePath);
|
|
|
+ try {
|
|
|
+ OutputStream os = new FileOutputStream(file);
|
|
|
+ try {
|
|
|
+ InputStream is = activity.getAssets().open(assetFilePath);
|
|
|
+ byte[] buffer = new byte[4096];
|
|
|
+ int bytesRead;
|
|
|
+ while ((bytesRead = is.read(buffer)) != -1) {
|
|
|
+ os.write(buffer, 0, bytesRead);
|
|
|
+ }
|
|
|
+ is.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ os.close();
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ return false;
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ //Log.i("FILEPATH", file.getAbsolutePath());
|
|
|
+ return true; // SUCCESS!
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void executePortRedirectionScript() {
|
|
|
+ assert(iptables); // we need iptables for our next trick
|
|
|
+ if (deployAsset("payload/redirect-ports.sh", "redirect-ports.sh")) {
|
|
|
+ String scriptFilePath = new File(MainActivity.getInstance().getFilesDir(), "redirect-ports.sh").getAbsolutePath();
|
|
|
+ Process p = null;
|
|
|
+ try {
|
|
|
+ p = new ProcessBuilder("su", "-c", "sh "+scriptFilePath).start();
|
|
|
+ p.waitFor(); // stall the main thread
|
|
|
+ // TODO: check return value?
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * @return name of porthack binary for device architecture
|
|
|
+ */
|
|
|
+ private static String getPorthackName() {
|
|
|
+ String porthack = "bind";
|
|
|
+
|
|
|
+ // determine system architecture to return the correct binary
|
|
|
+ String arch = System.getProperty("os.arch");
|
|
|
+ // TODO: handle more architectures
|
|
|
+ if (arch.equals("i686")) { // this is what genymotion reports
|
|
|
+ porthack += ".x86";
|
|
|
+ } else if (arch.startsWith("arm")) {
|
|
|
+ /*
|
|
|
+ possible values:
|
|
|
+ armv4
|
|
|
+ armv4t
|
|
|
+ armv5t
|
|
|
+ armv5te
|
|
|
+ armv5tej
|
|
|
+ armv6
|
|
|
+ armv7
|
|
|
+ */
|
|
|
+ porthack += ".arm";
|
|
|
+ } else if (arch.equals("mips")) {
|
|
|
+ porthack += ".mips";
|
|
|
+ }
|
|
|
+
|
|
|
+ return porthack;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return filepath to deployed porthack binary
|
|
|
+ */
|
|
|
+ public static String getPorthackFilepath() {
|
|
|
+ File file = new File(MainActivity.getInstance().getFilesDir(), getPorthackName());
|
|
|
+ return file.getAbsolutePath();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static boolean deployPorthack() {
|
|
|
+ String porthack = getPorthackName();
|
|
|
+ if (!deployAsset("payload/"+porthack, porthack)) {
|
|
|
+ return false; // :(
|
|
|
+ }
|
|
|
+
|
|
|
+ // make port hack executable
|
|
|
+ try {
|
|
|
+ Process p = new ProcessBuilder("su", "-c", "chmod 700 "+getPorthackFilepath()).start();
|
|
|
+ if (p.waitFor() != 0) {
|
|
|
+ logError(p.getErrorStream());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ logOutput(p.getInputStream());
|
|
|
+ } catch (IOException e) {
|
|
|
+ return false;
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true; // SUCCESS!
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void uninstallPorthack() {try {
|
|
|
+ Process p = new ProcessBuilder("su", "-c", "rm "+getPorthackFilepath()).start();
|
|
|
+ if (p.waitFor() != 0) {
|
|
|
+ logError(p.getErrorStream());
|
|
|
+ }
|
|
|
+ logOutput(p.getInputStream());
|
|
|
+ } catch (IOException e) {
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // copied from PrivilegedPort.java
|
|
|
+ private static void logOutput(InputStream stdout) throws IOException {
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
|
|
|
+ String line;
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
+ Log.i("HelperUtils", line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void logError(InputStream stderr) throws IOException {
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stderr));
|
|
|
+ Log.e("HelperUtils", reader.readLine());
|
|
|
+ }
|
|
|
}
|