本文主要以三星s6与s20二进制为样本进行分析。S6在2016年发布,这个版本由于有符号存在,可以大大降低逆向工程分析的难度,对于s20二进制只做部分参考分析。
内核Struct cred数据结构保存了进程的权限凭证如uid/gid、capability等,是内核漏洞攻击程序进行权限提升必须要更改的数据结构,因此对cred数据结构的保护至关重要。三星rkp在el2限制了cred在el1为只读,当内核对cred进行正常写操作时,通过rkp接口调用el2层函数对其进行写操作。但rkp除了对cred做只读保护外, 还在cred数据结构引入了两个字段bp_task和bp_pgd对cred做完整性校验以及struct task_security_struct结构加入bp_cred字段,防止被其他进程篡改。
struct cred {
当内核需要对cred进行创建和更新时,bp_task和bp_pgd就需要同步更新。
对于bp_task,内核在prepare_ro_creds函数里通过调用如下rkp接口:
drivers/uh/kdp.c
对应的el2层函数操作为:
对于bp_pgd,内核提供kdp_assign_pgd进行操作。
void kdp_assign_pgd(struct task_struct *p)
对应的el2层函数操作为:
security/selinux/hooks.c
内核调用cred_init_security对init进程进行初始化,后续子进程将会继承struct task_security_struct指针。当cred需要更改时同样使用prepare_ro_creds进行处理。
drivers/uh/kdp.c
对应的el2函数接口为:
Rkp加入这三个指针保护的目的是做完整性检查,在LSM框架里调用hook钩子之前加入判断语句:
security/security.c
cmp_sec_integrity用来验证cred数据结构的bp_task和bp_pgd指针是否被篡改。
Rkp对Namespace的保护目前仅局限于mount namespace,对其保护的方式为验证nsproxy->mnt_ns->root字段是否被篡改,同时还对mount挂载点进行了只读保护,不能挂载新的分区以及二进制程序必须从可信的mount点启动。
首先在vfsmount和mount数据结构中都加入了互相指向的backup指针:
include/linux/mount.h
内核通过调用kdp_mnt_alloc_vfsmount请求el2进行指针设置。
int kdp_mnt_alloc_vfsmount(struct mount *mnt)
对应的el2函数操作为:
当LSM框架的hook钩子执行时,会调用cmp_ns_integrity进行指针完整性检查:
static unsigned int cmp_ns_integrity(void)
当内核挂载一个新的文件系统时,调用kdp_do_new_mount->kdp_populate_sb来对指定的白名单分区做只读保护:
static void kdp_populate_sb(char *mount_point, struct vfsmount *mnt)
Rkp对以下的super_block结构体做了只读保护:
static struct super_block *rootfs_sb __kdp_ro = NULL;
对应的分区名白名单为:
#define KDP_MOUNT_SYSTEM "/system"
当内核通过execve执行一个新的二进制程序时,将会调用invalid_drive函数来判断二进制程序是否从以上白名单分区中启动:
int invalid_drive(struct linux_binprm *bprm)
kdp_check_path_mismatch忽略了以下白名单程序:
/com.android.runtime
kdp_check_sb_mismatch检查是否来自以上白名单分区。
static int kdp_check_sb_mismatch(struct super_block *sb)
Rkp在内核execve执行一个二进制程序时,对每个二进制在el2层做了一个标记,用于后续进行权限检查。
SYSCALL_DEFINE3(execve,
EL2对应的MARK_PPT操作为:
__int64 __fastcall rkp_mark_ppt(__int64 a1)
kdp_restrict_fork先判断二进制是否在白名单内:
/system/bin/patchoat
如果cred_kdp->type的第2个bit被置位, 则强制二进制程序的uid为2000。
Rkp在自身防护上有一些优势,可以大大增加逆向工程分析的难度。
Rkp对输出的字符串进行了哈希计算,大大增加了逆向过程的难度。
if ( !(unsigned int)sub_80001400(v6) || !(unsigned int)sub_80001400(v6 + *(unsigned int *)CRED_FLAGS_OFFSET - 1) )
Rkp对特定的cmd使用次数进行了限制,比如一些初始化函数只需执行一次, 这样可以防止rop/jop对其进行后续重用。
_int64 __fastcall rkp_main(unsigned __int64 a1, unsigned __int8 *a2)
[2]处在rkp_main执行完后对byte_422A8变量设置为1, 当下次rkp_main再次被调用时在[1]处会被执行检查, rkp_policy_violation函数打印”Multiple INIT calls”并panic系统。
三星s20使用的高通qhee平台使用了stack canary栈溢出保护机制。
在一些关键函数的开头插入如下代码:
在函数的末尾插入如下代码:
重新加载qword_80094A48值到x9寄存器,然后与之前保存的x8寄存器值进行比较,如果发生改变则跳转到loc_8000442DC去执行。
三星、苹果在发布的binary程序中,将黑客经常用到的gadget优化掉, 防止rop/jop利用。