My first Android phone Galaxy Note N7000 was purchased immediately after the announcement in October 2011. Thanks to one German craftsman under the nickname bauner, I had the opportunity to use the latest version of CyanogenMod (now LineageOS ). Until a year and a half ago, the phone died from a Chinese car charger.
I was looking for a replacement for a long time and stopped at Kyocera (yes, they also produce phones) KC-S701. It differs in brutal appearance and lack of touch buttons. I didn’t even think about root access to the phone, assuming that now every phone has the ability to get root in one way or another. And there is a craftsman who can port CyanogenMod for him. I was wrong.
In just one and a half years, only one update was released - a fixed kernel crash from a specially formed ping package. And Android KitKat a year ago was not the first freshness. Root did not receive access to this phone, and there was no information about it. I note that the same hardware is used in the American version of the Kyocera Brigadier E6782 phone, in which the fastboot mode is activated by default and there is no restriction on the launch of unsigned cores (just launch, not firmware, and only when using the unpatched bootloader, CVE-2014 -4325 ) and there is an opportunity to load into these modes by clamping the buttons of the phone. Through the efforts of Verizon (and maybe Kyocera?), The Android version on Brigadier has been upgraded to Lollipop.
So, I decided to deal with the process of getting root on Android on my own.
Two months ago, I did not know anything about the Android device (and now I don’t even know more). Most of the knowledge had to be obtained by studying source codes and experiments, since Information about hacking Android on the Internet is very small. The following description is valid for Android 4.4 KitKat, but it is possible that it will work on newer versions.
I want to draw your attention to the fact that this review describes only my specific experience of hacking Android on a particular phone model, so be extremely careful with using it in your practice if you don’t want to suddenly get a dead phone. Before starting research, I recommend that you forget that you use a hacked phone in everyday life and make a backup with a hard reset. This will protect your data when you make a mistake.
The article describes not only the actions that led to success, but also errors. I hope that my attempts to get to the bottom of the truth and the numerous rakes will be interesting to you.
All studies were conducted in a Linux environment.
With simple words, dirtycow (a working exploit for Android) allows you to replace the memory of any process (useful if you know ASM well) or any readable file, even if it is on the readonly file system. It is desirable that the file being replaced is less than or equal in size to the one being replaced. The main attack in dirtycow for Android is the substitution of /system/bin/run-as
, a kind of sudo
for debugging applications. Starting with the android-19 API (API and Android version correspondence table ) /system/bin/run-as
has CAP_SETUID and CAP_SETGID capabilities flags (older versions use the usual suid bit - 6755).
$ getcap bin/run-as bin/run-as = cap_setgid,cap_setuid+ep
If the file system is mounted in read-write mode, then everything that replaces dirtycow will end up on the file system. Therefore, it is necessary to make a backup of the original file and restore it after getting access, or not to remount the file system in read-write mode. As a rule, the / system partition in Android is by default mounted in read-only mode.
No wonder dirtycow is considered one of the most serious vulnerabilities discovered in Linux. And if you have knowledge with dirtycow, you can bypass all levels of kernel protection, including SELinux.
For starters, you can read about how SELinux contexts work. A nice article on the Gentoo wiki: https://wiki.gentoo.org/wiki/SELinux/Tutorials/How_does_a_process_get_into_a_certain_context
In short, then:
The only available way to get a relatively privileged shell in production Android devices is developer mode. Developer mode starts the adbd daemon, which can act as an analogue of ssh / telnet. In Android KitKat, it is located in initramfs along the path /sbin/adbd
and is not readable for non-root users. Initially, adbd runs as root and works in the SELinux context / init domain (used by the init process and usually has more privileges than other domains). If the process context is explicitly specified in /init.rc
, for example seclabel u: r: adbd: s0 , then the process starts immediately in the specified context. When initializing adbd, depending on the compilation parameters ( user, userdebug or eng, and Android parameters ( properties ), lowers privileges, namely, changes the current user from root to shell, sets the SELinux context to the shell, and cuts off all system capabilities except CAP_SETUID and CAP_SETGID for debugging applications via run-as
. The so-called Capability Bounding Set does not allow children to upgrade their capabilities, only lower them. These privileges allow you to do a little more than nothing on your phone. You can view the current process's capabilities using the cat /proc/self/status | grep CapBnd
. A p sshifrovat them with the command capsh
(not available on Android), for example:
$ capsh --decode=0000001fffffffff
The current SELinux context can be viewed with the id
command or cat /proc/self/attr/current
. The previous process context can be viewed with the cat /proc/self/attr/prev
command.
View context files: ls -Z
View the context of running processes: ps -Z
The first thing I did was using dirtycow for the intended purpose - I replaced /system/bin/run-as
, which set the UID / GID to 0 (su does the same thing). However, I could not mount the file system, even tmpfs. I also could not load kernel modules. View dmesg - no. I could not even browse directories that had 700 permissions and belonged to other system users. I could only read and write to block devices, and browsing files or directories was possible thanks to specifying the user’s UID / GID (I wrote my own bike - an analogue of su, which could set selinux context and user / group).
The first thing I did was dump all the 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 explore the dump with the utilities kpartx
and unpackbootimg
. The kpartx -a mmbblk0.img
creates a virtual block device accessible by the path /dev/mapper/loop0
. You can work with it as with any other block device. Dumps of the boot and recovery sections were unpacked with the unpackbootimg
utility.
Then I tried to write to recovery /dev/zero
, check and immediately restore from dump.
Since I could write to block devices, it means I could write custom recovery. I found TWRP from Brigadier, flashed it in recovery and rebooted into it adb reboot recovery
. TWRP I did not see, but only the Android icon with an exclamation mark. This is what a standard recovery looks like, which means TWRP has not flashed.
I reboot into normal mode, launch an exploit, check the hash recovery section - hash corresponds to the original. I try to write data again - hash has changed! I remember about page cache, I clean ( echo 3 > /proc/sys/vm/drop_caches
) - hash is old. Those. everything that I write to the block device flies without errors to / dev / null and sometimes lands in the Linux cache. But is the firmware update somehow happening? And user data is somehow written to internal memory. We must dig further.
At that time, I thought that all errors about the absence of privileges were caused by SELinux (I completely forgot that the capabilities can be trimmed). I did not see the logs of dmesg, logcat did not show anything relevant. And I started thinking how to disable SELinux.
The first clue I could find:
$ grep -A2 reload_policy boot/ramfs/init.rc on property:selinux.reload_policy=1 restart ueventd restart installd
Sources indicate that when this option is changed, init reloads SELinux policies from the /sepolicy
file.
Those. I can use dirtycow to overwrite /sepolicy
and use the setprop selinux.reload_policy 1
command setprop selinux.reload_policy 1
load the updated policy.
First you need to find out what is /sepolicy
. You can explore it with the sesearch
(the setools package in Debian).
$ sesearch --allow sepolicy $ sesearch --neverallow sepolicy $ sesearch --auditallow sepolicy $ sesearch --dontaudit sepolicy
In my case /sepolicy
contained only allow , which means - when enforcing SELinux mode on Android, it is allowed to do only what is declared in the policy. And the init process was only allowed to load the policy, but not disable it:
$ sesearch --allow sepolicy | grep 'load_policy' allow init kernel : security load_policy ;
My task was to allow the init context to set selinux-> enforce to permissive (setenforce 0).
The first thing I did was collect the standard Android stock policy, replace the original /sepolicy
, upload ( setprop selinux.reload_policy 1
as root) set and received a message in the status line that the phone is in unprotected mode. After that, the phone refused to launch applications, became very thoughtful, I also failed to set the permissive mode, and in the end the phone rebooted. A negative result is also a result, the replacement /sepolicy
worked.
The very first thought: the stock policy does not fit this phone and it starts to blunt in the absence of rights.
I put together a new policy in which I simply described all existing SELinux context and declared them permissive. It also did not help.
Then I decided to rebuild the policy and, if possible, add privileges to the shell context.
I found an article that says how to "decompile" a policy. A little tricky I was able to collect all the dependencies and run the sedump
utility. At the output, I received a text file that I was able to collect back (for KitKat checkpolicy -M -c 26 -o sepolicy.new policy.conf
) and even get a file with exactly the same size as the original sepolicy
, but with different hex contents. Downloading a new policy caused exactly the same results as before - the phone rebooted after a while.
I decided to put together two policies: from policy.conf
, which I received and from policy.conf
, which added all the privileges for allow init kernel : security
, including setenforce . Compare files in hex and, by analogy, replace bytes in the original sepolicy
.
As it turned out, two reassembled policies differed only in a couple of bytes. I began to look for similar matches in the original sepolicy , but did not find it. Then I just wrote a brute force script, which in the given range of displacements replaces the two bytes with "0xFF, 0xFF", starts sesearch --allow | grep " "
sesearch --allow | grep " "
. So I found the necessary bias in the original policy, replaced the bytes, replaced the original policy and nothing. Disabling selinux again failed.
A little later, I found the sepolicy-inject utility , which adds privileges to an already compiled sepolicy
file. If the rule already exists, adding maximum privileges does not increase the final size of the policy. Unfortunately, running the utility adds just one rule at a time. Wrote a script that adds maximum privileges for each rule. The result was a file with a policy in which each rule contained maximum privileges. File size is the same as the original. And this again did not help.
Then I learned that in Android there is a load_policy
command that reloads the policy in any way. Dances with a tambourine were superfluous.
adb shell run-as /data/local/tmp/run -u system -cu:r:init:s0 load_policy /data/local/tmp/sepolicy.new
It was possible to add any permissive domain, download a new policy and work in the context of this domain (by the way, chainfire supersu for new versions of Android works just like that ). But even this did not make it possible to disable SELinux. I decided to dig in the other direction.
Checking the difference between the boot and recovery sections. Everything is identical except initramfs. In the recovery section initramfs, I study init.rc, which describes only one service that runs /sbin/recovery
. I study strings sbin/recovery | less
strings sbin/recovery | less
, then the source code of the original recovery . As you can see, by default, recovery simply displays the Android logo. And if something needs to be done, then in the normal mode, the /cache/recovery/command
file is written to the /cache/recovery/command
partition, which may contain startup parameters for recovery. If we write --show_text
to this file, we should see the menu.
I run dirtycow exploit, set the UID / GID, write the file and run adb reboot recovery
. The phone restarts and I get to the standard recovery menu. Already something. I try to flash a zip file with supersu via adb sideload
. The operation is aborted with an error. I don’t really look at the error, but climb into the recovery code and look for the place responsible for checking the digital signature of the ZIP file.
I find out that initramfs contains the res/keys
public key in minicrypt format, which verifies the digital signature of the ZIP file. It turned out to be the standard Android test key , and that I can sign any archive with this key . You can check this as follows:
java -jar dumpkey.jar android/bootable/recovery/testdata/testkey.x509.pem > mykey diff -u mykey res/keys
I tried to install ZIP directly from sdcard, but an error occurred during recovery when mounting a sdcard. Studied etc/recovery.fstab
, it turned out that in recovery mode, sdcard is mounted as vfat:
$ grep mmcblk1 recovery/ramfs/etc/recovery.fstab /dev/block/mmcblk1p1 /sdcard vfat nosuid,nodev,barrier=1,data=ordered,nodelalloc wait
My 64Gb flash drive was formatted in exfat. I found an old sdcard on 2Gb, formatted it as vfat, recorded a ZIP, inserted it into my phone. Recovery this time was able to mount the card and I could view its contents on the phone. However, when installing the ZIP again, an error occurred: E: failed to set up expected mounts for install; aborting .
The strings recovery
team showed that this recovery is different from the stock one, at least there were strings related to Kyocera, and most likely to cleaning the /data
partition. Having rummaged in the original source code, I found out that the error I was interested in appears in the setup_install_mounts
function in the setup_install_mounts
file.
Those. before applying ZIP, recovery unmounts all partitions, but in my case something goes wrong.
The GPL license obliges smartphone manufacturers to upload kernel sources. Thanks to Linus and Stallman for this. Sometimes manufacturers put something left, sometimes the right source, but without a defconfig
file, sometimes right and very rarely with instructions on how to compile them (for example LG).
In my case, there were source codes with the correct defconfig
but without instructions. A little sweating, I was able to assemble the core and made sure that it was not a complete linden.
After a long time I stopped at two files:
Kyocera did not think for a long time, but simply wrote hooks on potentially dangerous operations on Android: mount
, umount
, insmod
(only one module is allowed to load - wlan
and only if it is loaded by the init process), and so on. This is where the recovery problem was. He could not unmount the file system /system
! These operations were allowed only to the init process. Including I could not disable SELinux because this feature was disabled when the kernel was compiled. It was possible to bypass these hooks only if the kernel was loaded with certain parameters ( kcdroidboot.mode = f-ksg or androidboot.mode = kcfactory , more about them later).
Also an interesting file to learn. It describes the possible options for downloading the phone:
0x77665500
- hex tag 00556677 in the sbl1 section)0x77665502
- hex label 02556677 in the sbl1 section)0x6f656d01
- hex tag 016d656f in the sbl1 section). What happens during this mode is set by the manufacturer. Judging by the source code, the phone reboots into this mode if the firmware authentication fails from the modem section.A little about how to boot on phones with a Qualcomm processor:
The built-in ROM boot loader Qualcomm (pbl - primary bootloader) loads the sbl1 partition (secondary bootloader). sbl1 loads tz (trust zone), then aboot (android boot, little kernel, lk). Aboot in turn loads boot, recovery or fota.
Description of sections involved in the boot:
All these sections are signed by a chain of certificates.
In some cases, it is useful to ignore firmware updates.
FOTA - firmware over the air. Unlike boot and recovery, fota is an unofficial Android boot mode. The task of fota is to update the firmware. Kyocera uses a solution from Red Bend for this, which in 35Mb can accommodate not only the kernel update but also the /system
partition. Therefore, writing to the /system
section is prohibited, otherwise applying a patch to the wrong data may mimic the phone.
There was an update on my phone. I could venture on it because I already had the opportunity to write to /cache
and interrupt the update at any time.
After examining the sources responsible for updating the Java application , it became clear to me how it happens:
/cache/delta/boot_delta.bin
, creates the file /cache/delta/Alt-OTA_dlcomplete
, confirming successful download of the file, and other files with headers.libjnialtota.so
is modified through the library fotamng
.The reboot does not happen instantly, so I have the opportunity to delete the file before rebooting and see what happens with the fotamng section.
I am writing a command that continuously dumps a partition and renames /cache/delta/boot_delta.bin
. I launch it immediately after the agreement to restart the phone. The phone reboots into FOTA mode, reports the absence of the update, and reboots into normal mode.
Begin to study the data that sdampil. In the /cache
section, I get fota logs, which even have dmseg logs! The reboot itself in the fota is initialized with the bytes "1" in the fotamng section:
$ 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
After a reboot, they are reset. In dmesg, I noticed the presence of the kernel parameter kcdroidboot.mode = f-ksg . Here it is! Those. bootloader removes protection for fota. And theoretically, if I write the boot partition to the fota and reboot the phone to this mode, then I will get a kernel with Kyocera protection disabled. But I still cannot write to system partitions.
What is in the aboot section is the Android downloader, the vanilla source code of which is located at: https://source.codeaurora.org/quic/la/kernel/lk/
There you can find information about how to load in some of the modes. For example, I found information that if you write "boot-recovery" in the misc section, you can reboot into recovery without adb reboot recovery . When loaded into recovery, this label is reset . And if recovery cannot boot, the phone will end up in the boot loop and you will lose it. So be careful, but rather avoid this reboot option.
There you can also find the code that translates the emmc system area into read-only mode. The answer to the question why it is impossible to overwrite recovery. This protection can be disabled from the Linux kernel. if you write the corresponding kernel module . Everything is already written by a friend from the land of the rising sun, who, it seems, also breathes unevenly to Kyocera telephones. The module did not work the first time, sometimes it hangs mmc in claim mode. Perhaps not everything is so clear and requires detailed research.
This is how the signature of the boot sections is verified: https://source.codeaurora.org/quic/la/kernel/lk/tree/platform/msm_shared/image_verify.c?h=LA.BR.1.3.3_rb2.29
Google helped me answer the question why I cannot read the kernel logs: /proc/sys/kernel/dmesg_restrict
. The value of this parameter is set to 1 while the phone is loading. If the user does not have CAP_SYS_ADMIN capability, then the logs are not available to him.
In my case, surprisingly, it was possible to set /sys/kernel/uevent_helper
. executable (shell script ), init init full capabilities.
:
#!/system/bin/sh echo 0 > /proc/sys/kernel/dmesg_restrict
/sys/kernel/uevent_helper
. dmesg!
Since Android, adbd , adbd . 70 Gb Android, . capabilities, , /sbin/adbd
root . , dmesg dmesg_restrict , , root . /system
.
, , lsh /sys/kernel/uevent_helper
. lsh , PATH
environment, .
WiFi . WiFi — . WiFi — . , WiFi . . , , , SELinux Amazon Fire Phone: https://github.com/chaosmaster/ford_selinux_permissive
, - Module.symvers. , , Module.symvers
, .
( 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
Kyocera.
wlan.c
... MODULE_NAME = wlan ...
(, wlan , lsmod), SELinux .
dmesg . , : /proc/sys/kernel/printk
, INFO , . : echo '8 8 8 8' > /proc/sys/kernel/printk
. , , SELinux .
, , WiFi. / WiFi Android.
SELinux , https://github.com/chaosmaster/ford_selinux_permissive , Kyocera hooks. kc_bootmode kc_kbfm .
Linux : cat /proc/kallsyms
. 0. . : echo 0 > /proc/sys/kernel/kptr_restrict
.
, 1. , kallsyms ( d D , ), . CONFIG_KALLSYMS_ALL
.
$ adb shell "grep kc_bootmode_setup /proc/kallsyms" c0d19d84 t kc_bootmode_setup
:
int (*_kc_bootmode_setup)(char *buf) = (int(*)()) 0xc0d19d84;
:
_kc_bootmode_setup("f-ksg")
:
_kc_bootmode_setup = (int (*)(char *buf))kallsyms_lookup_name("kc_bootmode_setup");
emmc /system
. , cache .
- SELinux. defined selinux_enabled
, hooks security_ops
.
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(); }
SELinux , , . .
int (*_enable_dload_mode)(char *str) = (int(*)()) 0xc0d0cc18; ... ... ... _enable_dload_mode("dload_mode");
download_mode
, . , usb mass storage device. Those. ! recovery.
, . mass storage . :
BS=512 nextblock=0 IMG=my-recovery.img DEST=/dev/sdb12 # 64 - total amount of 512*512b blocks for 16Mb partition (16Mb*1024*1024/(512*512)) for i in {1..64}; do echo $i echo dd if=${IMG} of=${DEST} bs=${BS} seek=${nextblock} skip=${nextblock} count=${BS} oflag=direct dd if=${IMG} of=${DEST} bs=${BS} seek=${nextblock} skip=${nextblock} count=${BS} oflag=direct nextblock=$((nextblock+BS)) echo "nextblock = ${nextblock}" sleep 0.5 done sync echo 3 > /proc/sys/vm/drop_caches
, adb reboot recovery
. . misc
, recovery .
/system
supersu . : root . WiFi , hooks. , WiFi . , .
. , :
boot . aboot ( binwalk -e aboot
), . , . boot , . aboot . sha256 . sha256, , .
#!/bin/bash # print der certificate: # openssl x509 -inform der -in 0xff.crt -text -noout # 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 # extract aboot signature # dd if=05-aboot.img of=signature.bin bs=1 count=256 skip=$(od -A d -t x4 05-aboot.img | awk --non-decimal-data '/^0000016/ { i=sprintf("%d\n","0x"$3); print (i+40)}') # extract base aboot image # 40 - aboot header size, refer to: https://android.googlesource.com/kernel/lk/+/caf/master/target/msm8226/tools/mkheader.c#160 # dd if=05-aboot.img of=aboot-base.img bs=1 count=$(od -A d -t x4 05-aboot.img | awk --non-decimal-data '/^0000016/ { i=sprintf("%d\n","0x"$3); print (i)}') skip=40 # how sha256 was calculated? # openssl dgst -sha256 -sign private_key -out signature.bin aboot-base.img ? NAME=$1 IMG=${NAME}/mynew.img SIG=${NAME}/signature.bin #IMG=aboot-base.img #SIG=signature.bin CALC_SHA256=$(sha256sum ${IMG} | awk '{print $1}') for i in `find . -name *.crt`; do ORIG_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}" != "" ]; then echo "sha256 was decrypted using ${i} key - ${ORIG_SHA256}" fi if [ "${ORIG_SHA256}" = "${CALC_SHA256}" ]; then echo "sha256 ${ORIG_SHA256}" echo "$i" fi done
boot fota, , fota . , .. bootloop, bootloop recovery. fota fotamng , .
, boot , fota , bootloop , , . boot , recovery . , recovery , boot. . ramdisk tags:
boot/recovery:
ramdisk: 0x01000000 tags: 0x00000100
fota:
ramdisk: 0x02000000 tags: 0x01e00000
Secure boot whitepaper Qualcomm , sha256 hash sha256 hash' ELF . Subject' . OU=05 00002000 SW_SIZE , sha256 hash 256 hash' 32 (0x2000/32=256). aboot ELF sbl1 (secondary boot loader).
little kernel Qualcomm, aboot. .
Kyocera Brigadier .
aboot . Subject' , . : aboot KC-S701 Brigadier. . , emmc Brigadier. , Brigadier KC-S701. fastboot. .
, " " — . Qualcomm QHSUSB__BULK
, , download mode, USB mass storage. . , .
boot fota? . , , .
Kyocera android system properties properties, . , bootloader'. libkcjprop_jni.so kcjprop_daemon . , .
, :
$ ls -la /sysprop/kcjprop/rw/8d9d788ddd5fecfdbc6c5f7c5cecfc -rw-rw---- root root 16 1970-01-22 21:01 8d9d788ddd5fecfdbc6c5f7c5cecfc
Kexec Linux . production Kexec, . user-end , . , — .
QSEE — Qualcomm, . — Trust Zone. .
. emmc, RPMB. SCM . RPMB (Replay Protected Memory Block) , lock/unlock .
, aboot Kyocera Propertiies github: https://github.com/kayrus/break_free .
, Kyocera . . . , .
PS (Nikolay Elenkov), Android security internals . bootloader' Android.
PPS , Justin Case, , , Qualcomm, .
Source: https://habr.com/ru/post/320150/
All Articles