二、真机内核利用适配
三、SELinux
四、CFI保护
五、BTI保护
六、PAC保护
七、MTE保护
八、AARCH64 JOP
九、总 结
/sys/fs/pstore/
目录下的日志文件以及dmesg
来获取内核最后崩溃时的寄存器值。根据寄存器信息来定位漏洞利用程序中需要适配的位置。
HOOK
。
HOOK
函数会在函数中被调用,它们一般以security_
开头。
security_
函数默认返回0让程序继续程序,如果开启了则跳转到HOOK
函数执行。
modprobe_path
或者core_pattern
后不能触发提权脚本的执行,这是因为我们指向的脚本不在SELinux规则中规定的可执行路径。为了绕过SELinux的检查,我们查看审计函数的代码,avc_has_perm
函数的子调用链为avc_has_perm->avc_has_perm_noaudit
。
avc_has_perm_noaudit
函数审计出当前的操作是被禁止的,那么调用avc_denied
函数。
avc_denied
函数来看,如果selinux_enforcing
全局变量为0,则仍然可以使得avc_denied
返回0,进而让selinux_
函数放行,因此可以利用漏洞改写selinux_enforcing
这个全局变量来绕过SELinux。
在高版本Linux中,判断方式采用了函数,实际上判断的是state->enforce
。
state
指针指向的仍然是一个全局变量结构体。
selinux_state.enforce
变量。
.cfi
结尾的函数。
.cfi
结尾的同名函数。
.cfi
的函数中只会有一条B跳转指令,不会再有其他任何人指令。实际上这些函数是一张类似于PLT跳转表的东西,我们可以把它命名为CFI表。
_cfi_slowpath
函数,_cfi_slowpath
函数调用_cfi_check
进行检查。
_cfi_check
根据_cfi_slowpath
函数的第一个参数传入的MAGIC
值,会再一次的判断函数指针是否能够通过检查。
__cfi_check_fail
函数让内核崩溃。
single_step_handler
附近。
CFI表
中函数的排列顺序是精心计算安排的,把一个函数指针所有可能的指向地址排列成相邻的。
CFI表
中跳转到错位的地址进而构造出ROP gadget
。如果是在x86
架构下,对于函数指针多值的CFI检查,由于指针值限定在CFI表的一个范围区间,可以在区间内寻找是否有合适的gadget
能够控制执行流。
ksocket pixel3
题目中,实现了一个自定义的socket
,我们可以通过UAF控制这个socket
对象的结构。由于开启了CFI,我们不能去控制函数指针。
close
时触发的avss_release
函数中有以下的链表unlink
操作:
unlink
用来做任意地址写,由于两个数据都必须为合法的内存指针,因此不能直接写数据。但是可以用错位的思路,CPU为小端,因此指针的最低一个字节存放在最前面,我们每次只需要保证指针的最低一个字节被写入到目标地址即可。令*(v3 + 112) = addr, *(v3 + 104) = bss | byte
,则可以在addr
处写上一个字节byte。其中bss为bss的地址,用于保证两个数据都为合法的内存指针不会崩溃。在实现了任意地址写以后,改写selinux_enforcing
为0关闭selinux,改写modprobe_path
为提权脚本。然后触发modprobe_path
的执行。
PAC*
类指令可以向指针中生成和插入 PAC。比如,PACIA X8,X9 可以在寄存器X8中以 X9 为上下文,APIAKey为密钥,为指针计算PAC,并且将结果写回到 X8 中。AUT*
类指令可以验证一个指针的 PAC。如果PAC是合法的,将会还原原始的指针。否则,将会在指针的扩展位中将会被写入错误码,在指针被间接引用时,会触发错误。比如,AUTIA X8,X9 可以以 X9 为上下文,验证 X8 寄存器中的指针。当验证成功时会将指针写回 X8,失败时则写回一个错误码。XPAC*
类指令可以移除一个指针的 PAC 并且在不验证指针有效性的前提下恢复指针的原始值。PAC的加密生成算法不同的硬件有不同的实现。
在Android中,开启了PAC保护的函数如图所示,PACIASP指令会基于当前的栈指针(SP)、私有密钥(APIAKey)以及返回地址生成认证码,认证码被嵌入到给定的函数返回地址中,在函数返回时,使用对应的 AUTIASP 指令对返回地址进行验证。如果地址合法且未被篡改,验证成功;否则,程序会触发异常(SIGILL 或其他非法指令异常)。
APIXKey_EL1
,用户态使用的密钥是APIXKey_EL0
,因此在用户态计算出的PAC值不能给内核态使用。内核态下可以操作访问APIXKey_EL1
、APIXKey_EL0
等寄存器修改或者读取密钥。
gadget
可以将用户态的APIXKey_EL0
修改成与内核态一样的数值,那么就可以在用户态执行PAC指令计算PAC值然后填入ROP链。
- IRG (Insert Random Tag) 指令为指针Xn生成一个随机tag,使用Xm作为种子,将结果保存至Xd中。
- STG (Store Allocation Tag) 指令将tag应用至内存中,生效的长度取决于颗粒度,一般为16字节。
- LDR (Load Register) 使用带有tag的指针读取内存。
malloc
后,通过对申请的堆地址打上标签返回,free
后对堆地址重新打标签。这样就能阻止UAF这类的漏洞,因为free
后指针重新打了标签,导致UAF残留的指针无效,通过UAF的指针访问内存时就会崩溃。不同的堆分配器在malloc
和free
时有着不同的处理内存标签的方式。有关内存分配器处理MTE标签的分析可以参考文章GeekCon的文章填补盾牌的裂缝:堆分配器中的MTE。
buf
指向的内存已经被free
导致重新打标签,现在传给Sys_write
的是一个无效的指针。
Error EL1h
。
el0t_64_sync
函数捕捉处理。
el0_svc
函数,并不会退出程序。
ret_to_user
返回到了用户态。
X30
寄存器指向的地址;而BLR指令在跳入新函数时,会将返回地址赋值给X30
寄存器。由于这个特性,我们在搜索一些gadgets
指令时,无需考虑BLR后面的代码。
在做GeekCon的kSysRace
赛题时,我们控制了一个地方的函数指针,能够调用任意一个函数,以及X0
执行的内容可控:
X19
指向X0
,因为X0
是我们可控的,我们不用担心BLR X8
返回执行后面,因为我们可以再调用一次BLR来将X30
覆盖。我们控制X8
,让其先跳入下面的代码:
X19
可控,我们可以调用3个参数的任意函数了,自始至终,我们的栈没有发生过调整,由于漏洞发生的位置栈尾部是这样的:
gadgets
一模一样,这意味着我们的gadgets
在执行到RET时可以直接返回到漏洞发生的函数的上层,栈平衡了。也就是我们能够执行任意的一个函数,控制3个参数,同时栈能够恢复,可以让程序继续保持正常的运行状态。这样我们就可以进行多次的任意函数调用。
link
、unlink
等操作去实现一个地址写或者读,本文还介绍了MTE保护机制的一种特殊情况下的爆破。
2. <a ""="" class="weapp_text_link js_weapp_entry" href="">填补盾牌的裂缝:堆分配器中的MTE
未经作者同意,不得转载
二进制安全研究员,BlackHat USA 2022 WASM演讲者,专注IOT、硬件、内核等方面的研究。
原文始发于微信公众号(奇安信天工实验室):Android保护机制及利用技巧总结
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论