Android Binder Attack Matrix: Exploitation of CVE-2023–20938 (Article — 2)

Utkarsh
4 min readJul 12, 2024
Exploitation Steps

This article will mostly be a diagram focused article since the exploitation steps are merely a proof-of-concept. Since these article series are targeted towards absolute beginners, it barely makes sense to discuss the exact steps, let alone describe the process textually. Hence, this article will mostly be a combination of diagrams and video recordings to allow beginners to obtain the information shared here efficiently.

Leak Primitive

static int binder_thread_read(...)
struct binder_transaction_data_secctx tr;
struct binder_transaction_data *trd = &tr.transaction_data;
...
trd->target.ptr = target_node->ptr; [1]
trd->cookie = target_node->cookie; [1]
...
if (copy_to_user(ptr, &tr, trsize)) { [2]
Leak Primitive State — 1
Leak Primitive State — 2
Leak Primitive State — 3

Step — 1: Exploit the vulnerability to free a binder_node.

Step — 2: Allocate a kernel object at the same memory location.

Step — 3: Leak values of a kernel object at offset 88 and 96.

Overlaying another kernel object, such as epitem, onto a freed binder_node in the Linux Kernel presents unique challenges. While userspace tasks are simpler—glibc's malloc uses a common freelist—the Linux Kernel's SLUB allocator allocates different object types from distinct caches, complicating manipulation. Recent security mitigations include CONFIG_SLAB_FREELIST_HARDENED, CONFIG_SLAB_FREELIST_RANDOM, and GFP_KERNEL_ACCOUNT (removed in version 5.9, reintroduced in 5.14). Additionally, eliminating cache aliasing for epitem (SLAB_ACCOUNT) and restricting unprivileged userfaultd for heap spraying enhance kernel security.

Our target version, 5.10, incorporates these measures, making exploitation more challenging.

Arbitrary Read

Use Leak Primitive to leak a pointer to a struct file.

ioctl(fd, FIGETBSZ, &value); //&value == argp

static int do_vfs_ioctl(struct file *filp, ...) {
...
struct inode *inode = file_inode(filp)
...
case FIGETBSZ;
...
return put_user(inode->i_sb->s_blocksize, (int __user *)argp);

Unlink Primitive

Step — 1: Exploit the vulnerability to create a dangling Node.

Step — 2: Allocate a fake binder_node with sendmsg() syscall (heap spray).

Step — 3: Use-after-free when decrementing the refcount of a binder_node.

static bool binder_dec_node_nilocked(struct binder_node *node, ...) {
...
// If binder_node's refcount reaches 0
// and satisfy all checks
...
hlist_del(&node->dead_node);
...

// *pprev = next
// *(next +8) = pprev
}

Root Privilege Escalation

Step — 1: Obtain an address to struct cred

Step — 2: Set uids & gids to 0

Step — 3: Set selinux_state.enforcing to 0 to disable SELinux

Step — 4: Seccomp and Linux capabilities (not covered)

Conclusion

And with that Ladies & Gentlemen, it’s a wrap! This article was diagram intensive to illustrate the exploit description allowing the Security Actors to understand and implement their own exploit codes to test these vulnerabilities on Android Binder. Feel free to reach out to me for clarifications and discussions. Until next time, Folks. Cheers!

--

--