How We Use Dirty Pipe To Get Reverse Root Shell On Android Emulator and Pixel 6
How We Use Dirty Pipe To Get Reverse Root Shell On Android Emulator and Pixel 6
How We Use Dirty Pipe To Get Reverse Root Shell On Android Emulator and Pixel 6
LiN
u TeamT5 D39 Vulnerability Researcher (Intern)
u NSYSU ISLAB
YingMuo
u TeamT5 D39 Vulnerability Researcher (Intern)
u Balsn CTF member
1
AGENDA
01 Dirty Pipe Intro
03 Bypass SELinux
04 On Pixel 6
05 Conclusion
Research
u CVE-2022-0847
u Linux kernel version > 5.8
u Correspond to Android 12
u Google Pixel 6
4
Pipe Splice & Zero copy
u Page Cache -> copy to userspace
u When use splice system call to do zero copy, instead of directly copy data to
pipe_buffer it will use index to find page cache and copy reference of page
to this cache and then copy data to pipe_buffer->page
u In order to avoid memory waste, pipe_buffer have a flag called
PIPE_BUF_FLAG_CAN_MERGE
5
Dirty Pipe Vulnerability
u In copy_page_to_iter_pipe
6
Dirty Pipe Vulnerability
After splice file
Before splice splice(fd,offset,pipe…)
struct pipe_buffer
pipe_buffer page
void *page
unsigned int len Page Page
unsigned int offset
…
unsigned int flag
Target Page
(File)
Dirty Pipe Vulnerability
Ex : splice(fd,offset(0),pipe,NULL,len(1),0) Trigger overwrite !
struct pipe_buffer
write(pipe,data,len)
void *page
unsigned int len = 1
pipe_write()
unsigned int offset = 0 Start writing offset
in page
… 1 = offset = buf->offset + buf->len
8
Dirty Pipe Attack Flow
1. Create pipe
2. Fill the pipe (set PIPE_BUF_FLAG_CAN_MERGE)
3. When pipe is full we can’t write
u Drain the pipe (leave the flag on structure)
9
Dirty Pipe Limitation
10
Env Different (Android)
u SElinux Protection
11
Environment (Emulator)
u android-12.1.0_r2
u sdk_phone_x86_64 (Android Emulator)
u BUILD_CONFIG=common/build.config.gki.x86_64
vendor_modprobe)
u We don’t find it in emulator but It seems to exist in Pixel 6 at other’s repo
12
Research
14
Android init
main.cpp
FirstStageMain
SetupSELinux
SecondStageMain
15
Android init epoll_wait
u After finish SecondStageMain, init will in a while loop
statement , waiting for event trigger
u Shutdown state
16
Hijack Android init process
u Since normal user don’t have the permission to access init binary
u Dirty pipe overwrite process mapping files -> Won’t trigger Copy On Write
on kernel
u Also init is a dynamic linked binary
u Overwrite library !
u Init is written in C++ lang, we can inject libc++.so to hijack its flow
u Find useless function in libc++.so
17
Hijack Android init process
1. Find the method to trigger epoll_wait event
2. Inject libc++.so ios_base::init
3. Hijack the flow that it process the event
18
Target we choose in libc++.so
__cxa_vec_delete
ios_base::init()
Design a jmp flow attack in libc++
libc++.so
0x50830
__cxa_vec_delete Useless Function
…
Shellcode
jmp 0x8B0F0 (locale)
0x6b066
ios_base::init() jmp jmp _ZNSt3_16localeC2Ev
0x8B0F0
_ZNSt3_16localeC2Ev
20
Design a jmp flow attack in libc++
libc++.so
0x50830
__cxa_vec_delete Useless Function
…
Shellcode
jmp 0x8B0F0 (locale)
0x6b066
ios_base::init() jmp jmp 0x50830
0x8B0F0
_ZNSt3_16localeC2Ev
21
Trigger Hijack Android init flow
SELinux Perm
LogMessage
Error
setprop a a ios_base::init()
(shell user) (overwrite jmp instruction)
Dirty Pipe write
Use for trigger init epoll_wait
__cxa_vec_delete()
(overwrite whole as shellcode)
pid != 1
return to locale
Fork()
__cxa_vec_delete() jmp(RIP+offset)
(overwrite whole as shellcode) pid != 0 pid == 0
mmap-> libui.so
(child)
jmp to libui.so
(exp2)
23
Prepare exp2
Will run with init permission
libui.so
Dirty pipe write & fork execve
(exp2)
24
Finish first stage
u Overwrite libc++.so
u Hijack init process
25
Research
Bypass SELinux
SELinux Intro
Context
u Label of subject or object
u Domain (subject)
27
SELinux Intro
Rule
u Rule scontext tcontext : class perm
read
init system_file
AV (access vector)
u Set of rules for specific s/tcontext
open
read
init write system_file
28
SELinux Intro
Policy
u All rules on system
Transition
u Change domain when execve a file
29
SELinux Intro
u Advantage
u Fine-grained access control
u No root
u Disadvantage
u Setting hardly
u No root
u Enforce permissive
u Permissive domain
30
SELinux check perm flow
31
SELinux check perm flow
32
avc_denied
Return –EACCESS
u Need CONFIG_SECURITY_SELINUX_DEVELOP
u avd’s flags AVD_FLAG_PERMISSIVE is on (permissive domain)
u Set if ebitmap_get_bit(policydb->permission_map, scontext->type) return true
33
Bypass
ebitmap_set_bit(policydb->permission_map, scontext->type, 1)
u Set permissive domain
34
Kernel module
Target
u ebitmap_set_bit(policydb->permission_map, scontext->type, 1)
init
u kprobe
u Find kallsyms_lookup_name
u kallsyms_lookup_name
u Needed function and global variable
u ebitmap_set_bit
35
Why choose init process
u init have root privilege
u SELinux rule
36
Load kernel module
37
Load kernel module
Can’t write
Kernel module
u Drity Pipe overwrite 1 page per time
u Kernel module has 3 pages size
u Library and kernel module are ELF, bytes at 0x0 are same
u Need bytes of kernel module and library are same at 0x1000, 0x2000
38
Load kernel module
u Kernel module
u Bytes at 0x1000 = 0x48
u Find /vendor/lib/camera.device@3.4-impl.so
39
Load kernel module
40
Sth bad QQ
u K
41
Flush avc
avc_lookup ebitmap_get_bit
u Not work if avc has cache Not find
u Call avc_ss_reset(state->avc) Find avd in policydb
Find avd
u Flush avc
42
Kernel module
u Target
u ebitmap_set_bit(policydb->permission_map, scontext->type, 1)
u avc_ss_reset(state->avc)
u init
u kprobe
u Find kallsyms_lookup_name
u kallsyms_lookup_name
u Needed function and global variable
u ebitmap_set_bit
u avc_ss_reset(state->avc)
43
Success !
44
libui.so exp2 set & run exp3
exp2 vendor_file
Run with init permission
(/system/lib64/libui.so)
(2)
Reverse
Execve toolbox /vendor/bin/toolbox shell
45
libui.so exp2 set & run exp3
exp2 vendor_file
Run with init permission init
(/system/lib64/libui.so)
kernel module
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
(2)
Reverse
Execve toolbox /vendor/bin/toolbox shell
46
libui.so exp2 set & run exp3
exp2 vendor_file
Run with init permission init
(/system/lib64/libui.so)
kernel module
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
(2)
exp3 Load kernel module
Reverse
Execve toolbox /vendor/bin/toolbox shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox (7) shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2)
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox (7) shell
avc_ss_reset
Dirty Pipe write
vendor_toolbox_exec
Run with vendor_modprobe
(2) Run without SELinux
exp3 Load kernel module
(3) Reverse
Execve toolbox /vendor/bin/toolbox (7) shell
56
Research
On Pixel 6
Policy on Pixel 6
58
Policy on Pixel 6
59
Path of module_load
60
Domain transition flow
Run with init permission
(/system/lib64/libui.so)
Transition to init-insmod-sh
Run with init-insmod-sh
Execute_no_trans
Run with init-insmod-sh
/vendor/bin/toolbox
61
Path of module_load
u Kernel module
u Bytes at 0x0 and 0x1000 are same with bypass kernel module
62
Bypass limitation of Dirty Pipe
u Section header
u Sections
63
Demo
64
Research
Conclusion
Conclusion
u Total attack flow
1. Use Dirty Pipe to inject library to hijack init process
2. Write kernel module for setting permissive domain
3. Use Dirty Pipe to load kernel module
4. Enjoy root without SELinux
u Dirty Pipe can be changed to any vulnerability that can arbitrarily write read-
only files
u The exploit has been tested on these firmware versions :
u SD1A.210817.036 (Success)
u SQ1D.220205.004 (Success)
u SP2A.220405.004 (Success)
u SP2A.220505.002 (Fail , Dirty Pipe patched)
66
Interesting things we saw
u We find a repo also used Dirty Pipe to do privilege escalation on Pixel 6.
u Similar exploit idea with repo but we have found something interesting!
1. We use less memory space to hijack init and make it more stable. In other
words, it won't crash if we don't patch libs.
2. Flush avc to prevent permissive domain not working.
3. We find different path to load kernel module by init-insmod-sh on Pixel 6.
4. Make kernel module have more libs choices by inserting some nop in kernel
module. ( 0x1000 -> CFI )
5. Make kernel module have more choices by patching ELF sections of kernel
module. (0x2000, 0x?000 )
67
Special thanks