diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c230db79..c0d260a3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,9 +11,11 @@
-
+
+
+
diff --git a/app/src/main/java/com/emanuelef/remote_capture/AppsResolver.java b/app/src/main/java/com/emanuelef/remote_capture/AppsResolver.java
index 47f81c31..2d773f95 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/AppsResolver.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/AppsResolver.java
@@ -112,7 +112,8 @@ public class AppsResolver {
try {
packages = mPm.getPackagesForUid(uid);
} catch (SecurityException e) {
- // this is a bug in some devices https://github.com/AdguardTeam/AdguardForAndroid/issues/173
+ // A SecurityException is normally raised when trying to query a package of another user/profile
+ // without holding the INTERACT_ACROSS_USERS/INTERACT_ACROSS_PROFILES permissions
e.printStackTrace();
}
diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
index cd9b2936..a6fd6fde 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
@@ -418,6 +418,12 @@ public class CaptureService extends VpnService implements Runnable {
Utils.showToast(this, R.string.vpn_setup_failed);
return abortStart();
}
+ } else {
+ // Root capture
+ if(checkCallingOrSelfPermission(Utils.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+ boolean success = Utils.rootGrantPermission(Utils.INTERACT_ACROSS_USERS);
+ Utils.showToast(this, success ? R.string.permission_granted : R.string.permission_grant_fail, "INTERACT_ACROSS_USERS");
+ }
}
mWhitelist = PCAPdroid.getInstance().getMalwareWhitelist();
@@ -1224,4 +1230,5 @@ public class CaptureService extends VpnService implements Runnable {
public static native void nativeSetFirewallEnabled(boolean enabled);
public static native int getNumCheckedMalwareConnections();
public static native int getNumCheckedFirewallConnections();
+ public static native int rootCmd(String prog, String args);
}
diff --git a/app/src/main/java/com/emanuelef/remote_capture/Utils.java b/app/src/main/java/com/emanuelef/remote_capture/Utils.java
index e312e388..3d2d631d 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/Utils.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/Utils.java
@@ -127,6 +127,7 @@ import javax.net.ssl.HttpsURLConnection;
public class Utils {
static final String TAG = "Utils";
+ public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
public static final int UID_UNKNOWN = -1;
public static final int UID_NO_FILTER = -2;
private static Boolean rootAvailable = null;
@@ -502,13 +503,13 @@ public class Utils {
return false;
}
- public static void showToast(Context context, int id) {
- String msg = context.getResources().getString(id);
+ public static void showToast(Context context, int id, Object... args) {
+ String msg = context.getResources().getString(id, (Object[]) args);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
- public static void showToastLong(Context context, int id) {
- String msg = context.getResources().getString(id);
+ public static void showToastLong(Context context, int id, Object... args) {
+ String msg = context.getResources().getString(id, (Object[]) args);
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
}
@@ -1182,4 +1183,8 @@ public class Utils {
tv.setText(text);
tv.setMovementMethod(LinkMovementMethod.getInstance());
}
+
+ public static boolean rootGrantPermission(String perm) {
+ return CaptureService.rootCmd("pm", String.format("grant %s %s", BuildConfig.APPLICATION_ID, perm)) == 0;
+ }
}
diff --git a/app/src/main/jni/common/utils.c b/app/src/main/jni/common/utils.c
index f72492df..a532346a 100644
--- a/app/src/main/jni/common/utils.c
+++ b/app/src/main/jni/common/utils.c
@@ -21,6 +21,8 @@
#include
#include
#include
+#include
+#include
#include "utils.h"
memtrack_t memtrack = {0};
@@ -28,6 +30,9 @@ int loglevel = 0;
const char *logtag = "pcapdroid-native";
void (*logcallback)(int lvl, const char *msg) = NULL;
+// Needed for local compilation, don't remove
+extern char **environ;
+
/* ******************************************************* */
void set_log_level(int lvl) {
@@ -191,3 +196,78 @@ void hexdump(const char *buf, size_t bufsize) {
out[idx] = '\0';
log_d("%s", out);
}
+
+/* ******************************************************* */
+
+int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error) {
+ int in_p[2], out_p[2];
+ int rv = -1;
+ pid_t pid;
+
+ if((pipe(in_p) != 0) || (pipe(out_p) != 0)) {
+ log_f("pipe failed[%d]: %s", errno, strerror(errno));
+ return -1;
+ }
+
+ if((pid = fork()) == 0) {
+ // child
+ char *argp[] = {"sh", "-c", as_root ? "su" : "sh", NULL};
+
+ close(in_p[1]);
+ close(out_p[0]);
+
+ dup2(in_p[0], STDIN_FILENO);
+ dup2(out_p[1], STDOUT_FILENO);
+ dup2(out_p[1], STDERR_FILENO);
+
+ execve(_PATH_BSHELL, argp, environ);
+ fprintf(stderr, "execve failed[%d]: %s", errno, strerror(errno));
+ exit(1);
+ } else if(pid > 0) {
+ // parent
+ int out = out_p[0];
+ close(in_p[0]);
+ close(out_p[1]);
+
+ // write "su" command input
+ log_d("run_shell_cmd[%d]: %s %s", pid, prog, args);
+ write(in_p[1], prog, strlen(prog));
+ write(in_p[1], " ", 1);
+ write(in_p[1], args, strlen(args));
+ write(in_p[1], "\n", 1);
+ close(in_p[1]);
+
+ waitpid(pid, &rv, 0);
+
+ if(check_error && (rv != 0)) {
+ char buf[128];
+ struct timeval timeout = {0};
+ fd_set fds;
+
+ buf[0] = '\0';
+ FD_ZERO(&fds);
+ FD_SET(out, &fds);
+
+ select(out + 1, &fds, NULL, NULL, &timeout);
+ if (FD_ISSET(out, &fds)) {
+ int num = read(out, buf, sizeof(buf) - 1);
+ if (num > 0)
+ buf[num] = '\0';
+ }
+
+ log_f("su \"%s\" invocation failed: %s", prog, buf);
+ rv = -1;
+ }
+
+ close(out_p[0]);
+ } else {
+ log_f("fork() failed[%d]: %s", errno, strerror(errno));
+ close(in_p[0]);
+ close(in_p[1]);
+ close(out_p[0]);
+ close(out_p[1]);
+ return -1;
+ }
+
+ return rv;
+}
\ No newline at end of file
diff --git a/app/src/main/jni/common/utils.h b/app/src/main/jni/common/utils.h
index 228354c4..110f280e 100644
--- a/app/src/main/jni/common/utils.h
+++ b/app/src/main/jni/common/utils.h
@@ -62,5 +62,6 @@ void tupleSwapPeers(zdtun_5tuple_t *tuple);
char loglvl2char(int lvl);
char* humanSize(char *buf, int bufsize, double bytes);
void hexdump(const char *buf, size_t bufsize);
+int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error);
#endif // __LOG_UTILS_H__
diff --git a/app/src/main/jni/core/capture_root.c b/app/src/main/jni/core/capture_root.c
index 2bf1a93d..4bfb557e 100644
--- a/app/src/main/jni/core/capture_root.c
+++ b/app/src/main/jni/core/capture_root.c
@@ -19,16 +19,11 @@
#include
#include
-#include
-#include
#include "pcapdroid.h"
#include "pcapd/pcapd.h"
#include "common/utils.h"
#include "third_party/uthash.h"
-// Needed for local compilation, don't remove
-extern char **environ;
-
#ifdef FUZZING
extern int openPcap(pcapdroid_t *pd);
extern int nextPacket(pcapdroid_t *pd, pcapd_hdr_t *hdr, char *buf, size_t bufsize);
@@ -50,81 +45,6 @@ typedef struct pcap_conn_t {
/* ******************************************************* */
-static int run_cmd(const char *prog, const char *args, bool as_root, bool check_error) {
- int in_p[2], out_p[2];
- int rv = -1;
- pid_t pid;
-
- if((pipe(in_p) != 0) || (pipe(out_p) != 0)) {
- log_f("pipe failed[%d]: %s", errno, strerror(errno));
- return -1;
- }
-
- if((pid = fork()) == 0) {
- // child
- char *argp[] = {"sh", "-c", as_root ? "su" : "sh", NULL};
-
- close(in_p[1]);
- close(out_p[0]);
-
- dup2(in_p[0], STDIN_FILENO);
- dup2(out_p[1], STDOUT_FILENO);
- dup2(out_p[1], STDERR_FILENO);
-
- execve(_PATH_BSHELL, argp, environ);
- fprintf(stderr, "execve failed[%d]: %s", errno, strerror(errno));
- exit(1);
- } else if(pid > 0) {
- // parent
- int out = out_p[0];
- close(in_p[0]);
- close(out_p[1]);
-
- // write "su" command input
- log_d("run_cmd[%d]: %s %s", pid, prog, args);
- write(in_p[1], prog, strlen(prog));
- write(in_p[1], " ", 1);
- write(in_p[1], args, strlen(args));
- write(in_p[1], "\n", 1);
- close(in_p[1]);
-
- waitpid(pid, &rv, 0);
-
- if(check_error && (rv != 0)) {
- char buf[128];
- struct timeval timeout = {0};
- fd_set fds;
-
- buf[0] = '\0';
- FD_ZERO(&fds);
- FD_SET(out, &fds);
-
- select(out + 1, &fds, NULL, NULL, &timeout);
- if (FD_ISSET(out, &fds)) {
- int num = read(out, buf, sizeof(buf) - 1);
- if (num > 0)
- buf[num] = '\0';
- }
-
- log_f("su \"%s\" invocation failed: %s", prog, buf);
- rv = -1;
- }
-
- close(out_p[0]);
- } else {
- log_f("fork() failed[%d]: %s", errno, strerror(errno));
- close(in_p[0]);
- close(in_p[1]);
- close(out_p[0]);
- close(out_p[1]);
- return -1;
- }
-
- return rv;
-}
-
-/* ******************************************************* */
-
static void kill_pcapd(pcapdroid_t *nc) {
int pid;
char pid_s[8];
@@ -138,7 +58,7 @@ static void kill_pcapd(pcapdroid_t *nc) {
if(pid != 0) {
log_d("Killing old pcapd with pid %d", pid);
- run_cmd("kill", pid_s, true, false);
+ run_shell_cmd("kill", pid_s, true, false);
}
fclose(f);
@@ -244,7 +164,7 @@ static int connectPcapd(pcapdroid_t *pd) {
// Start the daemon
char args[256];
snprintf(args, sizeof(args), "-l pcapd.log -i '%s' -d -u %d -t -b '%s'", pd->root.capture_interface, pd->app_filter, bpf);
- if(run_cmd(pcapd, args, pd->root.as_root, true) != 0)
+ if(run_shell_cmd(pcapd, args, pd->root.as_root, true) != 0)
goto cleanup;
// Wait for pcapd to start
diff --git a/app/src/main/jni/core/jni_impl.c b/app/src/main/jni/core/jni_impl.c
index b1ac52a3..60d1bf6b 100644
--- a/app/src/main/jni/core/jni_impl.c
+++ b/app/src/main/jni/core/jni_impl.c
@@ -793,6 +793,19 @@ Java_com_emanuelef_remote_1capture_CaptureService_nativeSetFirewallEnabled(JNIEn
/* ******************************************************* */
+JNIEXPORT int JNICALL
+Java_com_emanuelef_remote_1capture_CaptureService_rootCmd(JNIEnv *env, jclass clazz, jstring prog,
+ jstring args) {
+ const char *prog_s = (*env)->GetStringUTFChars(env, prog, 0);
+ const char *args_s = (*env)->GetStringUTFChars(env, args, 0);
+ int rv = run_shell_cmd(prog_s, args_s, true, true);
+
+ (*env)->ReleaseStringUTFChars(env, prog, prog_s);
+ (*env)->ReleaseStringUTFChars(env, args, args_s);
+
+ return rv;
+}
+
char* getStringPref(pcapdroid_t *pd, const char *key, char *buf, int bufsize) {
JNIEnv *env = pd->env;
@@ -904,5 +917,4 @@ void getApplicationByUid(pcapdroid_t *pd, jint uid, char *buf, int bufsize) {
if(obj) (*env)->DeleteLocalRef(env, obj);
}
-#endif // ANDROID
-
+#endif // ANDROID
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c5b29d34..7172d43c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -359,4 +359,6 @@
With the integrated Firewall you can easily block Internet access to individual apps and domains\n\nCombine this with the built-in traffic visibility to get the ultimate tool to protect your privacy
Enhance the security of your device with the malware detection feature\n\nBy using up-to-date blacklists, it can detect, block and alert malicious connections in real-time
PCAPdroid provides multiple ways to dump the traffic in the standard PCAP format for further analysis\n\nVia the trailer option, you can add app names to the packets and display them in Wireshark
+ %1$s permission was granted
+ %1$s permission could not be granted