My first Android phone, a Galaxy Note N7000, was bought just after the announcement in October 2011. Thanks to one German guy called bauner, I had an opportunity to use the latest version of CyanogenMod (now ). Unfortunately, my phone died after using a cheap Chinese car charger about one and a half years ago. LineageOS I spent a lot of time looking for a replacement and stopped at the (yes, they produce phones) KC-S701. It looks quite brutal and doesn’t have touch buttons. I did not even think about having root access to the phone. I was sure that nowadays every phone had a possibility to obtain root. And there will always be some guy who can port CyanogenMod to it. I was mistaken. Kyocera Within one and a half years, only one kernel update was released — resolved kernel failure on a specially crafted ping packet. Besides, one year ago Android KitKat was already considered to be pretty old. Unfortunately, there was neither info, nor possibility to get root access. The same hardware is used in the American version of the phone called Kyocera Brigadier E6782. It has fastboot mode by default and there is no way to boot unsigned kernels (boot only, not flash, and only using an old vulnerable bootloader, ) and besides it provides an opportunity to boot into fastboot and recovery modes by pressing the phone’s buttons. Through the efforts of Verizon (or maybe Kyocera?) The Android version of Brigadier has been updated to Lollipop. fix CVE-2014–4325 So, I decided to deal with the process of obtaining root on Android myself. Two months ago I did not know anything about the Android internals (actually, now ). Most of the knowledge was gained by reading the source code and by experiments, since there is a lack of information about the Android hacking on the Internet. The information below works on Android 4.4 KitKat, but I assume there is also a possibility to apply it on newer versions. I don’t know even more I would like to draw your attention to the fact that the info described in this article is only my personal experience of Android hacking on a particular phone model, so be careful using it if you do not want to brick the phone. If you wish to root your phone using this article, I recommend that you forget that you are using your phone in everyday life, make a backup, and then perform a hard reset. This will protect your data in case of fatal mistakes. The article describes not only the actions that led to success, but also mistakes. I hope that my attempts to get to the desired result, and numerous fails, will be interesting to you. The research was conducted in the Linux environment. Dirtycow (CVE-2016–5195) In simple words dirtycow ( for Android) allows you to replace the memory of any process (useful if you are familiar with the assembly), or any file available for reading, even if it is on the readonly filesystem. It is recommended to spoof the file which size is greater or equal to the size of the replacement. The main attack in dirtycow for Android is the spoofing. It is some kind of in Android which allows to debug applications. Since the API (see of matching API and Android versions) has and capabilities flags (in older versions suid bit is used - ): working exploit /system/bin/run-as sudo android-19 the table /system/bin/run-as CAP_SETUID CAP_SETGID 6755 $ getcap bin/run-asbin/run-as = cap_setgid,cap_setuid+ep If the file system is mounted in read-write mode, everything which was spoofed by dirtycow will be written to the file system. Thus you have to make a backup of the original file and restore it after gaining the root access, or just don’t remount the file system in read-write mode. Generally the partition in Android is mounted in read-only mode by default. /system That is why dirtycow is considered to be one of the most serious vulnerabilities found in Linux. And using the appropriate knowledge you can bypass all security levels of the Kernel, including SELinux. SELinux Beginners should know how the SELinux context works. There is a good article in the Gentoo wiki: https://wiki.gentoo.org/wiki/SELinux/Tutorials/How_does_a_process_get_into_a_certain_context In a nutshell, you should know the following: SELinux process context can be changed if such an operation is described in sepolicy rules (context transition). Android 4.4 (KitKat) provides the possibility to elevate privileges by changing the SELinux context. Since Android 5.x it is not possible anymore. There are file contexts. In addition to the process and file contexts, Android implemented its own . property_contexts The rules look as follows: source context (application) is allowed to access target context (filesystem). In SELinux enforcement mode applications are allowed to do only the explicitly granted operations. The rest is forbidden. Adbd and console The only possible way to obtain a partly privileged shell in production Android devices — developer mode. Developer mode starts adbd daemon which can act as some kind of ssh / telnet server. In Android KitKat binary is located in the and it is not readable for non-root users. Initially adbd is executed as the root user and it runs in SELinux context (used by init and usually has more privileges than other contexts). If the has the explicitly specified process context, such as , the process starts immediately in this context. In case of initializing, depending on compile options ( and Android settings ( ), adbd lowers the privileges: it changes the current user to shell, sets the SELinux context to and trims all system capabilities except and (that is required for debugging applications via ). The does not allow applications to escalate the capabilities, only to drop them. These privileges allow you to do a little bit more than nothing. You can view capabilities of the current process with the following command . And decrypt them with the command (not available on Android), for example.: /sbin/adbd initramfs u:r:init:s0 /init.rc seclabel u:r:adbd:s0 user, userdebug or eng properties u:r🐚s0 CAP_SETUID CAP_SETGID run-as Capability Bounding Set cat /proc/self/status | grep CapBnd capsh $ capsh --decode=0000001fffffffff0x0000001fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend You can view the current SELinux context using or commands. Previous context could be viewed by . id cat /proc/self/attr/current cat /proc/self/attr/prev View context of the files: ls -Z View context of the running processes: ps -Z Get root access The first thing I did was to use dirtycow for its intended purpose — to spoof the , which allows me to set UID / GID to 0 (same stuff does). However, I could not mount the file systems (as well as tmpfs), could not load kernel modules and view . I could not even browse the directory which had the 0700 permissions and belonged to other system users. I could only read and write to a block device. Viewing the files or directories was possible only when appropriate UID / GID were set to a specified user (invented my own - alternative, which sets SELinux context and user / group. This helped me to understand internals). /system/bin/run-as su dmesg wheel su Then I made the dump of the entire firmware, boot and recovery: $ dd if=/dev/block/mmcblk0 of=/storage/sdcard1/mmcblk0.img$ dd if=/dev/block/platform/msm_sdcc.1/by-name/boot of=/storage/sdcard1/boot.img$ dd if=/dev/block/platform/msm_sdcc.1/by-name/recovery of=/storage/sdcard1/recovery.img You can examine the full dump using utilities such as and . The command creates a virtual block device which is available by path. You can work with your dump like with a regular block device. Dumps of the boot and recovery partitions can be unpacked using the . kpartx [unpackbootimg](https://github.com/osm0sis/mkbootimg) kpartx -a mmbblk0.img /dev/mapper/loop0 unpackbootimg Then I tried to the recovery, just to check whether write operations work, and then immediately restored recovery from the dump. zero If I could write into block devices, then I could also write the custom recovery. I found the TWRP of Brigadier, flashed it into recovery partition and restarted the phone: . I didn't see anything related to TWRP, only Android icon with an exclamation mark. It looked like a standard Android recovery, not like TWRP. adb reboot recovery I rebooted my phone into normal mode, ran the exploit and checked the hash of the recovery partiton — hash matched the original. I tried to write the data again — hash changed! Then I remembered about the Linux page cache and flushed it ( ) - hash value matched the original again. Thus everything I wrote to a block device was "redirected" into without any error and sometimes settled in the Linux cache. But how does the firmware update work? And how is the user data stored into the internal memory? I had to dig further. echo 3 > /proc/sys/vm/drop_caches /dev/null Trying to disable SELinux At that time I thought that all the restrictions were caused by the lack of SELinux privileges (I totally forgot about the dropped capabilities). I could not view dmesg, logcat didn’t show anything relevant. I started to think how to disable SELinux. The first clue I could find: $ grep -A2 reload_policy boot_initramfs/init.rcon property: selinux.reload_policy = 1 restart ueventd restart installd says that when you change this option, init rereads and reloads the SELinux policy from the file. Source code /sepolicy Since I’m using dirtycow I can overwrite the and execute the command to reload the updated policy. /sepolicy setprop selinux.reload_policy 1 First of all you have to figure out what is inside the file. You can read its rules using the command ( package in Debian). /sepolicy sesearch setools $ sesearch --allow sepolicy$ sesearch --neverallow sepolicy$ sesearch --auditallow sepolicy$ sesearch --dontaudit sepolicy In my case the file contained only rules which means - when SELinux is in Enforcement mode applications are allowed to do only what is granted in the policy. And thus init process is allowed to reload the policy, but it is not allowed to change the enforcement mode: /sepolicy allow $ sesearch --allow sepolicy | grep 'load_policy' allow init kernel: security load_policy; My goal was to allow init context to set the enforcement mode into permissive (setenforce 0). The first step I did: built a standard policy from the stock Android KitKat, replaced the original , loaded (being root: ) and received a message in the status bar that the phone was in the unprotected mode (more details about this notification later). After that the phone refused to run applications, became very thoughtful, besides I was still unable to set the permissive mode and thus the phone eventually rebooted. A negative result is also a result,the replacement worked. /sepolicy setprop selinux.reload_policy 1 /sepolicy My first thought: the stock policy does not fit this phone and it starts glitching due to lack of permissions. Then I decided to rebuild the original policy and add as much privileges to the shell context as possible. I found an which explained how to “reverse engineer” the policy. I was able to resolve all the dependencies and run the utility. As a result I received a text file which I was able to compile back into the binary format ( for KitKat) and even got the file with exactly the same size as the original , but different hex content. Loading of the new policy file caused exactly the same results as before - the phone rebooted after a couple of minutes. article [sedump](https://github.com/kayrus/sedump/) checkpolicy -M -c 26 -o sepolicy.new policy.conf sepolicy I decided to compile two policies from the following files: original decompiled and with all the privileges inside the , including . The comparison of these files could tell me which bytes I have to replace in the original binary file. policy.conf policy.conf allow init kernel: security setenforce sepolicy It turned out that only two bytes were changed. I tried to find the match in the original but I could not. Then I just wrote a brute force script which replaces two bytes to “0xFF, 0xFF”, launches and if it doesn't meet the result, tries to replace the bytes on the next incremented offset and so on. After a couple of minutes the script found the necessary offset in the original policy. I replaced the bytes, spoofed the original policy on the phone. At this time it worked fine and didn't reboot. But I still could not disable the SELinux enforcement mode. sepolicy sesearch --allow | grep "desired result" A bit later I found a utility which could modify binary file. It could add the new permissive SELinux context or add capability into the existing rule. While adding the permissive context increases the file, modification of the existing rule does not increase the size. Unfortunately, the utility adds only one permission per run. I had to write another script which granted all the capabilities to each rule. The new policy file size matched the original one. But again, the policy reload did not help. sepolicy-inject sepolicy sepolicy Then I noticed that Android had command which reloaded the policy from any path: load_policy # it has to be executed under system user# since /sys/fs/selinux/policy is owned by system user in my phoneadb shell run-as /data/local/tmp/run -u system -c u:r:init:s0 load_policy /data/local/tmp/sepolicy.new or this way: $ run-as /data/local/tmp/run -u system -c u:r:init:s0 sh -c "cat my_policy > /sys/fs/selinux/load" You can add any permissive domain, load the new policy and work in the context of this domain (by the way, supersu from chainfire in the same manner for new Android versions). But even this didn’t give me the possibility to disable SELinux. I decided to dig in another direction. works Investigating recovery I started from checking the difference between boot and recovery partitions. They were identical except for initramfs. The initramfs of the recovery partition has which has only one service which executes . Investigation of the output and reading of the gave the following results: init.rc /sbin/recovery strings sbin/recovery | less original recovery source code the default recovery simply displays the Android logo and then reboots after timeout if you want to “enter” the recovery, you have to create the file with corresponding command inside, i.e. which will show the recovery menu. /cache/recovery/command --show_text I wrote the file and executed the command. The phone rebooted and I was able to see the standard recovery menu. At least some result. I tried to flash the supersu ZIP file via . The operation terminated with an error. I did not really look at this error and started to investigate the recovery code responsible for the ZIP digital signature verification. adb reboot recovery adb sideload It turns out that the recovery initramfs contains a public key in format which checks the ZIP file digital signature. The public key appeared to be a standard Android and so I can sign any ZIP file using this key. You can check this key using the commands below: res/keys minicrypt test key $ java -jar dumpkey.jar android/bootable/recovery/testdata/testkey.x509.pem > mykey$ diff -u mykey res/keys I tried to install the ZIP directly from sdcard, but the recovery caused an error while mounting the sdcard. Investigation of the file showed the actual problem: the sdcard in the recovery mode was mounted as vfat: etc/recovery.fstab $ grep mmcblk1 recovery/ramfs/etc/recovery.fstab/dev/block/mmcblk1p1/sdcard vfat nosuid, nodev, barrier = 1, data = ordered, nodelalloc wait My 64Gb sdcard was formatted in exFAT. I found an old 2Gb sdcard, reformatted it as vfat, wrote the ZIP and inserted it into the phone. This time the recovery was able to mount the volume and I could view its contents on the phone. However, the ZIP installation caused an error again: . E: failed to set up expected mounts for install; aborting The command showed that this recovery had custom Kyocera strings, at least there were strings related to the partition wipe command. After reading the original source code I found that the error was caused in the function of the file. For some reason the recovery failed to unmount all the partitions listed in . strings recovery | less /data setup_install_mounts [roots.cpp](https://android.googlesource.com/platform/bootable/recovery/+/android-4.4.2_r2%20/roots.cpp#206) recovery/ramfs/etc/recovery.fstab Investigating kernel sources Unlike the AOSP’s Apache license, the GPLv2 license requires from smartphones’ manufacturers to publish the Linux kernel source. Thanks to Linus and Stallman for this opportunity. Sometimes manufacturers publish a fake source code, sometimes a correct one, but without the file, sometimes with and very rarely with the instructions on how to build the source code (e.g. LG). defconfig defconfig In my case the source code was distributed with the correct but without instructions. Thus I spent some time to build the . defconfig kernel After a long time of the source code investigation I stopped at two files: https://github.com/kayrus/kc-s701-torque-kernel/blob/master/security/selinux/hooks.c https://github.com/kayrus/kc-s701-torque-kernel/blob/master/arch/arm/mach-msm/restart.c hooks To increase the phone’s security Kyocera just implemented custom SELinux hooks on potentially dangerous operations: , , (the only module allowed to be loaded is and only if it is loaded by init process or non-root user) and some others. That's the actual reason why the recovery failed. It could not unmount the partition! The mount/unmount operations of the partition are allowed only for the init process. In particular, I could not disable SELinux because this feature was disabled at the kernel compilation. These hooks could be bypassed only if the kernel was loaded with certain boot parameters ( or ). mount umount insmod wlan /system /system kcdroidboot.mode=f-ksg androidboot.mode=kcfactory restart This file describes possible reboot options for the phone: — fastboot mode, not available in my phone ( - hex mark in partition) adb reboot bootloader 0x77665500 00556677 sbl1 — default Android recovery mode ( - hex mark in partition) adb reboot recovery 0x77665502 02556677 sbl1 — the so-called . I did not understand what this was for, there was no hex mark in . Probably it relates to adb reboot rtc ALARM_BOOT sbl1 https://developer.android.com/reference/android/app/AlarmManager.html (in my case oem-1, - hex mark in partition). The manufacturer defines what happens in this mode. According to the sources, the phone restarts into this mode in case of failed firmware files' verification which are located in partition. adb reboot oem-X 0x6f656d01 016d656f sbl1 modem — emergency download, reboots into a default Qualcomm download mode. The phone is identified as COM port, which can be used to boot a custom bootloader (but it should be signed by the private key corresponding to the phone model ), and perform low-level operations with your phone, including flashing, unlocking, etc. Usually used in conjunction with QPST application. For some phones these bootloaders have already leaked into the Internet. adb reboot edl QHSUSB__BULK A certain which could be triggered by kernel boot parameter. Looks quite interesting. download mode Some info on how Qualcomm based phones are booted: Built-in ROM Qualcomm bootloader (pbl — primary bootloader) verifies and boots partition (secondary bootloader). verifies and boots (trust zone), then (Android boot, little kernel, lk). Then aboot can boot into built-in fastboot maintenance mode, do the normal boot or boot into recovery or fota. sbl1 sbl1 tz aboot Partition description involved at boot: tz — Qualcomm Trust Zone. It performs low-level operations, including working with QFuses (rpmb secured mmc partition). rpm — Resource and Power Manager firmware. Firmware for specialized SoC, responsible for resources and power. sdi — trust zone storage partition. The data which is used by Trust Zone. All of these partitions are signed by a certificate chain. fota In some cases it is useful to ignore firmware updates. FOTA — firmware over the air. Unlike the boot or recovery, fota is an unofficial Android boot mode. Fota task is to update the firmware. Kyocera uses proprietary solution which fits the whole update in 35Mb. It includes , , , and even partitions' updated. That is why the partition is available in read-only mode. If you modify this partition, the diff-based fota update can brick the phone. Red Bend boot tz recovery fota system /system An update for my phone was available since September 2015. I didn’t update my phone because I was afraid to loose an opportunity to root my phone. Now I can easily execute the update process since I have full access to partition and abort the update procedure at any time. /cache After examining the source code of the responsible update tool, it became clear to me how it functioned: Java based Java app downloads a special delta file into , creates a file and verifies whether delta file was successfully downloaded. /cache/delta/boot_delta.bin /cache/delta/Alt-OTA_dlcomplete When you confirm the update procedure it verifies the file again. If previous verification succeeds then partition is modified using the dynamic library. fotamng libjnialtota.so Phone reboots. The reboot does not happen instantly, so I can delete a file before the reboot and see what will happen with the partition. fotamng I wrote the script which continuously made partition dump and renamed file. I ran it immediately after the update confirmation. The phone rebooted into FOTA mode, showed an error and rebooted into normal boot mode. fotamng /cache/delta/boot_delta.bin I started to investigate the dumped data. The partition also contained a bonus: fota and dmseg logs! It appeared that fota boot could be initialized by couple of bytes set to "1" in partition: /cache fotamng $ dd if=/data/local/tmp/one_bit.bin of=/dev/block/platform/msm_sdcc.1/by-name/fotamng seek=16 bs=1 count=1$ dd if=/data/local/tmp/one_bit.bin of=/dev/block/platform/msm_sdcc.1/by-name/fotamng seek=24 bs=1 count=1$ dd if=/data/local/tmp/one_bit.bin of=/dev/block/platform/msm_sdcc.1/by-name/fotamng seek=131088 bs=1 count=1$ dd if=/data/local/tmp/one_bit.bin of=/dev/block/platform/msm_sdcc.1/by-name/fotamng seek=131096 bs=1 count=1 These bytes are cleared after the reboot. I also noticed the kernel parameter in fota dmesg logs. Here it is! Thus, bootloader removes the phone protection for fota boot and theoretically if I write a regular boot partiton instead of fota and reboot the phone into this mode, I will get the kernel with disabled Kyocera protection. But I still don’t have a write access to the system-related partitions. kcdroidboot.mode=f-ksg Investigating the little kernel (lk) sources Little kernel or Android bootloader is located inside the aboot partition. Vanilla source code is available at: https://source.codeaurora.org/quic/la/kernel/lk/ There you can find information on how to boot into some of the modes. For example, if you write the into the partition, the next boot will be the and there is no need to execute . When you boot into the recovery using this method the label . If the recovery can not be booted, the phone will get the boot loop and you'll lose it. So be careful and preferably avoid this option to reboot. boot-recovery misc recovery mode adb reboot recovery boot-recovery will be reset You can also find the code which enables of the the system-related emmc area. This is an answer to the question why it is impossible to rewrite the recovery partition. This protection can be disabled in the Linux kernel. And such a module was already by another guy who was interested in Kyocera phones. This module works from time to time and sometimes hangs on mmc claim function. Ideally it requires the detailed investigation. read-only protection written Here is how aboot verifies boot partitions: https://source.codeaurora.org/quic/la/kernel/lk/tree/platform/msm_shared/image_verify.c?h=LA.BR.1.3.3_rb2.29 The first successes Google helped me to answer the question why I could not read the kernel log: . The value of this parameter is set to 1 while the phone boots. If the user does not have capability, the logs are not available for it. [/proc/sys/kernel/dmesg_restrict](https://www.kernel.org/doc/Documentation/sysctl/kernel.txt) CAP_SYS_ADMIN uevent_helper In my case, surprisingly, I had the possibility to write into . If you write the path to some executable, it will be executed under root user, init SELinux context and most importantly with full capabilities (shell script also works) /sys/kernel/uevent_helper I wrote the following script: #!/system/bin/shecho 0 > /proc/sys/kernel/dmesg_restrict Uploaded it on the phone, wrote its path into and I got the possibility to read dmesg logs! /sys/kernel/uevent_helper Patched adbd Since I could not easily investigate the phone’s internals because of the capabilities restriction I decided to build my own adbd with blackjack and hookers. To do this I had to download 70 Gb of Android source code (I didn’t want to mess with each dependency individually). I removed the check which drops the capabilities, compiled adbd, replaced the and received a full root console. Now I can mount filesystems, read dmesg logs without messing with , easily view or edit files which are not owned by root, and much more. But I still can not mount the partition and load modules into the kernel. /sbin/adbd dmesg_restrict /system By the way, this procedure can be avoided by compiling the and writing its path into the . I suggest starting lsh wrapped in a script which sets the environment, otherwise you'll have to specify the full path to each command. lsh /sys/kernel/uevent_helper PATH WiFi WiFi in my phone works through the kernel module. When WiFi is turned on — the module is loaded. When WiFi is turned off — the module is unloaded. If you replace the module file with your own and turn on WiFi — your spoofed module must be loaded. Fortunately, my phone doesn’t check modules’ digital signatures. The first thing I tried was to compile and load the module which disabled the SELinux by replacing the kernel memory. The module was initially written for Amazon Fire Phone: https://github.com/chaosmaster/ford_selinux_permissive You need to have a more or less appropriate kernel sources and file in order to compile the module. If the source code explicitly corresponds to the kernel which is used in the phone, then you can use the which is automatically generated during the kernel compilation process. Module.symvers Module.symvers If the kernel module complains on , you will need to extract the from the partition. This could be done using script: disagrees about version of symbol module_layout Module.symvers boot https://github.com/glandium/extract-symvers $ unpackbootimg -i boot.img -o boot$ extract-symvers.py -e le -B 0xc0008000 boot/boot.img-zImage > %PATH_TO_KERNEL%/Module.symvers Do you remember this ? The module should be called . Here is how I resolved this problem: list wlan Created a symlink wlan.c Modified Makefile ...MODULE_NAME = wlan... After applying these tricks and executing the the module has successfully loaded (memory used by the module reduced, it could be checked using command), but SELinux was not disabled. svc wifi disable && svc wifi enable wlan lsmod The dmesg logs did not contain any information related to the new module. It was caused by another kernel option: which filters INFO logs including modules' logs. I lowered the threshold for all logs: , reloaded the module and it appeared that the module just could not find the required bytes pattern. I decided to write own kernel module. /proc/sys/kernel/printk echo '8 8 8 8' > /proc/sys/kernel/printk Writing a module Disabling security protection I failed to disable SELinux, but by analogy with module I could try to disable Kyocera hooks. I just needed to set a or variable using the Linux kernel module. https://github.com/chaosmaster/ford_selinux_permissive kc_bootmode kc_kbfm The Linux kernel has the possibility to get the pointers’ addresses of all the functions and variables: . By default, these addresses are displayed as 0. It is yet another kernel protection and can be disabled by the following command: . cat /proc/kallsyms echo 0 > /proc/sys/kernel/kptr_restrict Once you get the address of the desired function, you can call it with the appropriate parameter and function will set the corresponding variable to 1. I noticed that not all Linux kernels display addresses for the variables ( or types, case says whether the variable is public or not) that is why I used function pointers, but not actual variable pointers. Perhaps it is determined by the option during the kernel compilation. d D CONFIG_KALLSYMS_ALL $ adb shell "grep kc_bootmode_setup /proc/kallsyms"c0d19d84 t kc_bootmode_setup First of all I had to declare the kernel function I’d like to call in the module: int (* _kc_bootmode_setup) (char * buf) = (int (*) ()) 0xc0d19d84; And then call it: _kc_bootmode_setup("f-ksg") You can also determine the addresses dynamically: _kc_bootmode_setup = (int (*) (char * buf)) kallsyms_lookup_name("kc_bootmode_setup"); I loaded the and it disabled the protection! Now I can mount the and load any kernel module regardless of its name. module /system Protected eMMC area is still in read-only mode and it does not allow you to modify partition on a regular basis. The files can be edited, but when you clean the kernel cache everything goes to its original state. /system Finally disabling SELinux It was already not required but just for fun, I decided to finally disable the SELinux. I could not modify the defined constant, but I could dereference the structure with hooks pointers. selinux_enabled security_ops This can be done by calling the function: reset_security_ops void (* _reset_security_ops) (void) = NULL;... ... ..._reset_security_ops = (void (*) (void)) kallsyms_lookup_name("reset_security_ops");if (_reset_security_ops! = NULL) { _reset_security_ops ();} It disables all the SELinux hooks and functions, but the system still thinks that SELinux is enabled since contains 1 integer value. Thus there may be some issues related to SELinux functions, i.e. incorrect output. selinux_enabled ls -Z Reboot into download mode int (* _enable_dload_mode) (char * str) = (int (*) ()) 0xc0d0cc18;... ... ..._enable_dload_mode("dload_mode"); The same operation works with I wrote above. After the module loading the phone reboot will boot it in a special mode which operates as USB mass storage device. Thus I have full access to all the phone's partitions! I tried to overwrite the recovery partition and it worked even after clearing the kernel cache. download_mode Actually regular doesn't work and phone's USB mass storage device disconnects and the writing stops. Perhaps this is a result of an internal cache overflow of the mass storage loader. I had to write a workaround. An advanced script for the heavy partition is available here: . The primary trick is to read the data which you'd like to overwrite. Reading is much faster than writing, so I compare the source and destination hashes and if they differ I write new data. This saves lot of time and 1.2Gb modified partition can be flashed in 2-3 minutes instead of 35 minutes. dd system https://github.com/kayrus/kc_s701_break_free/blob/master/inject_supersu/write_rooted_system.sh Using this method I the binaries into the downloaded partition and flashed it back into the phone. The phone booted, but I got the following message in the notification bar: . I had already seen this message before when I loaded insecure SELinux policy. This notification also causes the permanent red LED blinking and the phone doesn't turn off the screen on timeout (I guess to pay the user's attention that the phone was hacked). I spent some time and figured out that this notification was triggered by daemon. It is a small daemon which monitors / binaries which can be found within , monitors SELinux state and whether partition is mounted in read-write mode. It writes the security states into the and triggers the notification mentioned above. I just disabled this service, but there should be a better fix to disable / detection only and allow to monitor the state since I still would like to control my phone's security. installed supersu /system Low security level. Inappropriate application may have been installed. Please uninstall it and reboot the phone. /system/vendor/bin/akscd su sudo PATH /system /data/system/akscd/out_%s.dat su sudo /system The initial task is completed: permanent root access and possibility to write into the external sdcard were obtained. In addition I wrote a which sets UID with the capability, uloads original module then loads my false module which disables the security and finally loads the original module again. utility system CAP_SYS_MODULE wlan wlan wlan My next goal was to boot custom kernel. I hoped there was no digital signature verification and I decided to boot my custom boot partition. Since it is dangerous to flash regular boot partition, I decided to flash it into the recovery partition and reboot into the recovery using command. . Unfortunately, the phone could not boot into recovery, it just vibrated and then rebooted into normal mode. adb reboot recovery Remember the **misc** partition, it is not recommended to boot into recovery through the **boot-recovery** entry in this partition, it can cause a boot loop Looks like I have to unlock the phone’s bootloader. There is not much info on how to do this, but I found a couple of methods which worked for old phones: . https://github.com/beaups/SamsungCID https://github.com/djrbliss/loki https://bits-please.blogspot.com/2016/02/unlocking-motorola-bootloader.html Digital signatures of the aboot and boot partitions I was curious how exactly aboot verifies the boot partitions. So I unpacked all the certificates from aboot partition ( ), extracted images signatures and looped over all the public keys trying to decrypt the signature. It turned out that all the boot images were signed using the same key. binwalk -e aboot #!/bin/bash # mkdir boot# unpackbootimg -i 09-boot.img -o boot# cd boot# mkbootimg --kernel 09-boot.img-zImage --ramdisk 09-boot.img-ramdisk.gz --cmdline "`cat 09-boot.img-cmdline`" --base `cat 09-boot.img-base` --pagesize `cat 09-boot.img-pagesize` --dt 09-boot.img-dtb --kernel_offset `cat 09-boot.img-kerneloff` --ramdisk_offset `cat 09-boot.img-ramdiskoff` --tags_offset `cat 09-boot.img-tagsoff` --output mynew.img# dd if=../09-boot.img of=signature.bin bs=1 count=256 skip=$(ls -la mynew.img | awk '{print $5}')# cd ..# binwalk -e 05-aboot.img# openssl rsautl -raw -inkey <(openssl x509 -pubkey -noout -inform der -in _05-aboot.img.extracted/4D8D8.crt 2>/dev/null) -pubin -in signature.bin 2>/dev/null | hd# print cert in text mode: openssl x509 -inform der -in 1768B.crt -text -noout NAME=$1IMG=${NAME}/mynew.imgSIG=${NAME}/signature.bin CALC_SHA256=$(sha256sum ${IMG} | awk '{print $1}') for i in `find . -name *.crt`; doORIG_SHA256=$(openssl rsautl -inkey <(openssl x509 -pubkey -noout -inform der -in ${i} 2>/dev/null) -pubin -in ${SIG} 2>/dev/null | hexdump -ve '/1 "%02x"')if [ "${ORIG_SHA256}" != "" ]; thenecho "sha256 was decrypted using ${i} key - ${ORIG_SHA256}"fiif [ "${ORIG_SHA256}" = "${CALC_SHA256}" ]; thenecho "sha256 matched the calculated sha256 ${ORIG_SHA256}"echo "$i"fidone This script prints the following output: $ ./verify.sh bootsha256 was decrypted using ./_05-aboot.img.extracted/31464.crt key - 91642909810cde935881d1656f6290ebf32e19975d99d739bd03162f79e000d7 sha256 matched the calculated sha256 91642909810cde935881d1656f6290ebf32e19975d99d739bd03162f79e000d7 ./_05-aboot.img.extracted/31464.crt Verification of the aboot partition appeared to be more complicated. I was able to extract and decrypt the sha256 signature of the image. But could not calculate this hash myself. Fortunately, Nikolay Elenkov, the author of the helped and forwarded me to Qualcomm whitepaper: . It explains how target sha256 is calculated. The hash depends on and which are defined inside the certificate's subject, i.e. Android security internals https://www.qualcomm.com/media/documents/files/secure-boot-and-image-authentication-technical-overview.pdf HW_ID SW_ID Subject: C=US, ST=CA, L=San Diego, OU=07 0001 SHA256, OU=06 001E MODEL_ID, OU=05 00002000 SW_SIZE, OU=04 0039 OEM_ID, OU=03 0000000000000002 DEBUG, OU=02 009180E10039001E HW_ID, OU=01 0000000000000009 SW_ID, O=Kyocera wireless corp CSMS, CN=Ayano Nakamura The working script is available in Nikolay’s . github repository Experimenting with the fota partition Since I know that boot/recovery and fota partitions are signed with the same key and the fota partition is booted with the disabled kernel security it’s worth trying to check whether flashing the boot partition into the fota partition can work. It was quite risky and I could get bootloop similar to recovery bootloop. The boot-in-fota sign is written into the partition and if aboot did not boot the fota, it could try to boot it in the endless loop. fotamng Unfortunately, the boot partition written in fota could not be loaded, but fortunately LK resets fota bytes on boot and I did not face the bootloop. It is not clear why it doesn’t work, perhaps because of the different ramdisk and tag offsets (most probably they are hardcoded inside the LK): boot / recovery: ramdisk: 0x01000000 tags: 0x00000100 fota: ramdisk: 0x02000000 tags: 0x01e00000 Experimenting with the Brigadier bootloader For the experiments I ordered a Kyocera Brigadier with a broken screen. I checked the digital signatures of KC-S701 and Brigadier aboot partitions and it appeared that they were signed by the certificates with the same subject, so aboot partitions should be interchangeable. I decided to make an experiment: flash aboot from KC-S701 into the Brigadier. The bootloader successfully booted. Surprisingly, the eMMC write protection was not activated and I could easily restore the original bootloader (I suppose read-only flag can be set by QFuses). Then I tried to flash the aboot from Brigadier into the KC-S701. I could get the opportunity to use fastboot and boot any unsigned kernel. At this time the phone didn’t boot. At this point the story could end with the “phone didn’t boot” and a black screen. But, fortunately, this black screen was the “download mode”. I was able to flash the original aboot partition and the phone was resurrected. I still wonder why it didn’t boot. Both certificate chains are valid and theoretically should be interchangeable. Most probably I faced this issue because Brigadier is based on msm8928 and KC-S701 is based on msm8226 SoC. They are both from one SoC family but have some minor differences (msm8928 supports USB-OTG). What should still be clarified Why sepolicy recompilation doesn’t work? Imperfect sepolicy decompiler? What does aboot partition hide? What hides behind the reboot mode? Aboot partition contains the code, at least: , , , , and commands. How can I enter the fastboot mode? oem-1 fastboot flash erase oem device-info preflash oem enable-charger-screen oem disable-charger-screen How to disable camera shutter sound? I’ve found at least 3 options which somehow control camera shutter sound but none of these options works. The only way to disable it is to replace the file. But I don't like this solution and still want to find the way to disable this option in a more elegant way. Most probably it is controlled by proprietary Kyocera properties. /system/media/audio/ui/camera_click.ogg Explanation Kyocera properties Kyocera along with Android system properties uses its own internal properties. I’m pretty sure there can be some tricky options which can influence the removal of bootloader protection or camera shutter sound which can not be disabled (oh man, I just realized how many restrictions are there in KC-S701). The phone has a dynamic library and the daemon. I can write an app which will use this library, but comparing to other TODOs this task has low priority. libkcjprop_jni.so kcjprop_daemon Options are written into the filesystem and look like binary data: $ ls -la /sysprop/kcjprop/rw/8d9d788ddd5fecfdbc6c5f7c5cecfc -rw-rw ---- root root 16 1970-01-22 21:01 8d9d788ddd5fecfdbc6c5f7c5cecfc Kexec allows the Linux kernel to load another kernel. By default, the production kernel releases don’t support Kexec, but it is possible to enable it using the kernel module. Then, using the user-end utility you can load any custom kernel which will replace the current one. It looks like a hack, but if you want to load your custom kernel and bypass digital signature verification — this is an option. Kexec QSEE vulnerability QSEE — Qualcomm TrustZone protection which could have a vulnerability to execute custom code within the TrustZone context: . Looks like I have to build a specially formatted SCM command to burn the corresponding QFuse and unlock the bootloader. Still to be done. https://bits-please.blogspot.com/2016/05/qsee-privilege-escalation-vulnerability.html Conclusion Every problem I resolve causes an amount of new problems and it is hard to predict how deep the rabbit hole goes. Modules’ source code, aboot loaders and the library to work with the Kyocera Properties are available in my github repo: . https://github.com/kayrus/kc_s701_break_free I would like to express my gratitude to the Kyocera developers for the excellent devices and high security. Otherwise, this article would not have been written. On the other hand, the lack of regular updates makes me really upset. If Kyocera has a new phone model with the ability to unlock the bootloader, I will certainly buy it. I still haven’t given up to unlock the bootloader. The most profitable time was on Christmas, but now it is hard for me to find some free time and focus on this problem. P.S. Many thanks to . He explained to me how Android boot chain works and helped me with the aboot digital signature verification. Nikolay Elenkov