Monday, September 10, 2018

OATmeal on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Universal Cereal Bus: Exploiting Android phones over USB

Posted by Jann Horn, Google Project Zero

Recently, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re has been some attention around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 topic of physical attacks on smartphones, where an attacker with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to connect USB devices to a locked phone attempts to gain access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data stored on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device. This blogpost describes how such an attack could have been performed against Android devices (tested with a Pixel 2).

After an Android phone has been unlocked once on boot (on newer devices, using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "Unlock for all features and data" screen; on older devices, using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "To start Android, enter your password" screen), it retains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 encryption keys used to decrypt files in kernel memory even when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 screen is locked, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 encrypted filesystem areas or partition(s) stay accessible. Therefore, an attacker who gains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to execute code on a locked device in a sufficiently privileged context can not only backdoor cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device, but can also directly access user data.
(Caveat: We have not looked into what happens to work profile data when a user who has a work profile toggles off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 work profile.)

The bug reports referenced in this blogpost, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corresponding proof-of-concept code, are available at:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1583 ("directory traversal over USB via injection in blkid output")
https://bugs.chromium.org/p/project-zero/issues/detail?id=1590 ("privesc zygote->init; chain from USB")

These issues were fixed as CVE-2018-9445 (fixed at patch level 2018-08-01) and CVE-2018-9488 (fixed at patch level 2018-09-01).

The attack surface

Many Android phones support USB host mode (often using OTG adapters). This allows phones to connect to many types of USB devices (this list isn't necessarily complete):

  • USB sticks: When a USB stick is inserted into an Android phone, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 user can copy files between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 USB stick. Even if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device is locked, Android versions before P will still attempt to mount cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 USB stick. (Android 9, which was released after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se issues were reported, has logic in vold that blocks mounting USB sticks while cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device is locked.)
  • USB keyboards and mice: Android supports using external input devices instead of using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 touchscreen. This also works on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lockscreen (e.g. for entering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PIN).
  • USB ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet adapters: When a USB ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet adapter is connected to an Android phone, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 phone will attempt to connect to a wired network, using DHCP to obtain an IP address. This also works if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 phone is locked.

This blogpost focuses on USB sticks. Mounting an untrusted USB stick offers nontrivial attack surface in highly privileged system components: The kernel has to talk to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 USB mass storage device using a protocol that includes a subset of SCSI, parse its partition table, and interpret partition contents using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's filesystem implementation; userspace code has to identify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem type and instruct cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to mount cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device to some location. On Android, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace implementation for this is mostly in vold (one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 processes that are considered to have kernel-equivalent privileges), which uses separate processes in restrictive SELinux domains to e.g. determine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem types of partitions on USB sticks.

The bug (part 1): Determining partition attributes

When a USB stick has been inserted and vold has determined cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list of partitions on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device, it attempts to identify three attributes of each partition: Label (a user-readable string describing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 partition), UUID (a unique identifier that can be used to determine whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 USB stick is one that has been inserted into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device before), and filesystem type. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 modern GPT partitioning scheme, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se attributes can mostly be stored in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 partition table itself; however, USB sticks tend to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MBR partition scheme instead, which can not store UUIDs and labels. For normal USB sticks, Android supports both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MBR partition scheme and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GPT partition scheme.

To provide cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to label partitions and assign UUIDs to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m even when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MBR partition scheme is used, filesystems implement a hack: The filesystem header contains fields for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se attributes, allowing an implementation that has already determined cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem type and knows cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem header layout of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 specific filesystem to extract this information in a filesystem-specific manner. When vold wants to determine label, UUID and filesystem type, it invokes /system/bin/blkid in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blkid_untrusted SELinux domain, which does exactly this: First, it attempts to identify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem type using magic numbers and (failing that) some heuristics, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n, it extracts cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 label and UUID. It prints cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 results to stdout in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following format:

/dev/block/sda1: LABEL="

However, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 version of blkid used by Android did not escape cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 label string, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code responsible for parsing blkid's output only scanned for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first occurrences of UUID=" and TYPE=". Therefore, by creating a partition with a crafted label, it was possible to gain control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UUID and type strings returned to vold, which would ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise always be a valid UUID string and one of a fixed set of type strings.

The bug (part 2): Mounting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem

When vold has determined that a newly inserted USB stick with an MBR partition table contains a partition of type vfat that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's vfat filesystem implementation should be able to mount, PublicVolume::doMount() constructs a mount path based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem UUID, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n attempts to ensure that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mountpoint directory exists and has appropriate ownership and mode, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n attempts to mount over that directory:

   if (mFsType != "vfat") {
       LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
       return -EIO;
   }
   if (vfat::Check(mDevPath)) {
       LOG(ERROR) << getId() << " failed filesystem check";
       return -EIO;
   }
   // Use UUID as stable name, if available
   std::string stableName = getId();
   if (!mFsUuid.empty()) {
       stableName = mFsUuid;
   }
   mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());
   [...]
   if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
       PLOG(ERROR) << getId() << " failed to create mount points";
       return -errno;
   }
   if (vfat::Mount(mDevPath, mRawPath, false, false, false,
           AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {
       PLOG(ERROR) << getId() << " failed to mount " << mDevPath;
       return -EIO;
   }

The mount path is determined using a format string, without any sanity checks on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UUID string that was provided by blkid. Therefore, an attacker with control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UUID string can perform a directory traversal attack and cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FAT filesystem to be mounted outside of /mnt/media_rw.

This means that if an attacker inserts a USB stick with a FAT filesystem whose label string is 'UUID="../##' into a locked phone, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 phone will mount that USB stick to /mnt/##.

However, this straightforward implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attack has several severe limitations; some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m can be overcome, ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs worked around:

  • Label string length: A FAT filesystem label is limited to 11 bytes. An attacker attempting to perform a straightforward attack needs to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 six bytes 'UUID="' to start cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 injection, which leaves only five characters for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 directory traversal - insufficient to reach any interesting point in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mount hierarchy. The next section describes how to work around that.
  • SELinux restrictions on mountpoints: Even though vold is considered to be kernel-equivalent, a SELinux policy applies some restrictions on what vold can do. Specifically, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mounton permission is restricted to a set of permitted labels.
  • Writability requirement: fs_prepare_dir() fails if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target directory is not mode 0700 and chmod() fails.
  • Restrictions on access to vfat filesystems: When a vfat filesystem is mounted, all of its files are labeled as u:object_r:vfat:s0. Even if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem is mounted in a place from which important code or data is loaded, many SELinux contexts won't be permitted to actually interact with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem - for example, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote and system_server aren't allowed to do so. On top of that, processes that don't have sufficient privileges to bypass DAC checks also need to be in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 media_rw group. The section "Dealing with SELinux: Triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug twice" describes how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se restrictions can be avoided in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of this specific bug.

Exploitation: Chameleonic USB mass storage

As described in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous section, a FAT filesystem label is limited to 11 bytes. blkid supports a range of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r filesystem types that have significantly longer label strings, but if you used such a filesystem type, you'd cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n have to make it past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fsck check for vfat filesystems and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem header checks performed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel when mounting a vfat filesystem. The vfat kernel filesystem doesn't require a fixed magic value right at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 start of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 partition, so this might cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365oretically work somehow; however, because several of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 values in a FAT filesystem header are actually important for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel, and at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same time, blkid also performs some sanity checks on superblocks, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PoC takes a different route.

After blkid has read parts of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem and used cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m to determine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem's type, label and UUID, fsck_msdos and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in-kernel filesystem implementation will re-read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same data, and those repeated reads actually go through to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 storage device. The Linux kernel caches block device pages when userspace directly interacts with block devices, but __blkdev_put() removes all cached data associated with a block device when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last open file referencing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device is closed.

A physical attacker can abuse this by attaching a fake storage device that returns different data for multiple reads from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same location. This allows us to present, for example, a romfs header with a long label string to blkid while presenting a perfectly normal vfat filesystem to fsck_msdos and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in-kernel filesystem implementation.

This is relatively simple to implement in practice thanks to Linux' built-in support for device-side USB. Andrzej Pietrasiewicz's talk "Make your own USB gadget" is a useful introduction to this topic. Basically, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel ships with implementations for device-side USB mass storage, HID devices, ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet adapters, and more; using a relatively simple pseudo-filesystem-based configuration interface, you can configure a composite gadget that provides one or multiple of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se functions, potentially with multiple instances, to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 connected device. The hardware you need is a system that runs Linux and supports device-side USB; for testing this attack, a Raspberry Pi Zero W was used.

The f_mass_storage gadget function is designed to use a normal file as backing storage; to be able to interactively respond to requests from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Android phone, a FUSE filesystem is used as backing storage instead, using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 direct_io option / cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 FOPEN_DIRECT_IO flag to ensure that our own kernel doesn't add unwanted caching.

At this point, it is already possible to implement an attack that can steal, for example, photos stored on external storage. Luckily for an attacker, immediately after a USB stick has been mounted, com.android.externalstorage/.MountReceiver is launched, which is a process whose SELinux domain permits access to USB devices. So after a malicious FAT partition has been mounted over /data (using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 label string 'UUID="../../data'), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote forks off a child with appropriate SELinux context and group membership to permit accesses to USB devices. This child cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n loads bytecode from /data/dalvik-cache/, permitting us to take control over com.android.externalstorage, which has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 necessary privileges to exfiltrate external storage contents.

However, for an attacker who wants to access not just photos, but things like chat logs or aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ntication credentials stored on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device, this level of access should normally not be sufficient on its own.

Dealing with SELinux: Triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug twice

The major limiting factor at this point is that, even though it is possible to mount over /data, a lot of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 highly-privileged code running on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device is not permitted to access cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mounted filesystem. However, one highly-privileged service does have access to it: vold.

vold actually supports two types of USB sticks, PublicVolume and PrivateVolume. Up to this point, this blogpost focused on PublicVolume; from here on, PrivateVolume becomes important.
A PrivateVolume is a USB stick that must be formatted using a GUID Partition Table. It must contain a partition that has type UUID kGptAndroidExpand (193D1EA4-B3CA-11E4-B075-10604B889DCF), which contains a dm-crypt-encrypted ext4 (or f2fs) filesystem. The corresponding key is stored at /data/misc/vold/expand_{partGuid}.key, where {partGuid} is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 partition GUID from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GPT table as a normalized lowercase hexstring.

As an attacker, it normally shouldn't be possible to mount an ext4 filesystem this way because phones aren't usually set up with any such keys; and even if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is such a key, you'd still have to know what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct partition GUID is and what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 key is. However, we can mount a vfat filesystem over /data/misc and put our own key cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re, for our own GUID. Then, while cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first malicious USB mass storage device is still connected, we can connect a second one that is mounted as PrivateVolume using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 keys vold will read from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first USB mass storage device. (Technically, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ordering in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last sentence isn't entirely correct - actually, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit provides both mass storage devices as a single composite device at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same time, but stalls cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first read from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second mass storage device to create cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 desired ordering.)

Because PrivateVolume instances use ext4, we can control DAC ownership and permissions on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 filesystem; and thanks to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way a PrivateVolume is integrated into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system, we can even control SELinux labels on that filesystem.

In summary, at this point, we can mount a controlled filesystem over /data, with arbitrary file permissions and arbitrary SELinux contexts. Because we control file permissions and SELinux contexts, we can allow any process to access files on our filesystem - including mapping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m with PROT_EXEC.

Injecting into zygote

The zygote process is relatively powerful, even though it is not listed as part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 TCB. By design, it runs with UID 0, can arbitrarily change its UID, and can perform dynamic SELinux transitions into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux contexts of system_server and normal apps. In ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r words, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote has access to almost all user data on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device.

When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 64-bit zygote starts up on system boot, it loads code from /data/dalvik-cache/arm64/system@framework@boot*.{art,oat,vdex}. Normally, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 oat file (which contains an ELF library that will be loaded with dlopen()) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vdex file are symlinks to files on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 immutable /system partition; only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 art file is actually stored on /data. But we can instead make system@framework@boot.art and system@framework@boot.vdex symlinks to /system (to get around some consistency checks without knowing exactly which Android build is running on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device) while placing our own malicious ELF library at system@framework@boot.oat (with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux context that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 legitimate oat file would have). Then, by placing a function with __attribute__((constructor)) in our ELF library, we can get code execution in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote as soon as it calls dlopen() on startup.

The missing step at this point is that when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attack is performed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote is already running; and this attack only works while cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote is starting up.

Crashing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system

This part is a bit unpleasant.

When a critical system component (in particular, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote or system_server) crashes (which you can simulate on an eng build using kill), Android attempts to automatically recover from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash by restarting most userspace processes (including cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote). When this happens, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 screen first shows cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 boot animation for a bit, followed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lock screen with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "Unlock for all features and data" prompt that normally only shows up after boot. However, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 key material for accessing user data is still present at this point, as you can verify if ADB is on by running "ls /sdcard" on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device.

This means that if we can somehow crash system_server, we can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n inject code into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following userspace restart and will be able to access user data on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device.

Of course, mounting our own filesystem over /data is very crude and makes all sorts of things fail, but surprisingly, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system doesn't immediately fall over - while parts of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UI become unusable, most places have some error handling that prevents cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system from failing so clearly that a restart happens.
After some experimentation, it turned out that Android's code for tracking bandwidth usage has a safety check: If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network usage tracking code can't write to disk and >=2MiB (mPersistThresholdBytes) of network traffic have been observed since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last successful write, a fatal exception is thrown. This means that if we can create some sort of network connection to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n send it >=2MiB worth of ping flood, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n trigger a stats writeback by eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r waiting for a periodic writeback or changing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 state of a network interface, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device will reboot.

To create a network connection, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are two options:

  • Connect to a wifi network. Before Android 9, even when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device is locked, it is normally possible to connect to a new wifi network by dragging down from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 top of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 screen, tapping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 drop-down below cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wifi symbol, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n tapping on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 name of an open wifi network. (This doesn't work for networks protected with WPA, but of course an attacker can make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir own wifi network an open one.) Many devices will also just autoconnect to networks with certain names.
  • Connect to an ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet network. Android supports USB ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet adapters and will automatically connect to ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet networks.

For testing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, a manually-created connection to a wifi network was used; for a more reliable and user-friendly exploit, you'd probably want to use an ecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rnet connection.

At this point, we can run arbitrary native code in zygote context and access user data; but we can't yet read out cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 raw disk encryption key, directly access cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 underlying block device, or take a RAM dump (although at this point, half cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 data that would've been in a RAM dump is probably gone anyway thanks to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system crash). If we want to be able to do those things, we'll have to escalate our privileges a bit more.

From zygote to vold

Even though cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote is not supposed to be part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 TCB, it has access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CAP_SYS_ADMIN capability in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initial user namespace, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux policy permits cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use of this capability. The zygote uses this capability for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mount() syscall and for installing a seccomp filter without setting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 NO_NEW_PRIVS flag. There are multiple ways to abuse CAP_SYS_ADMIN; in particular, on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Pixel 2, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following ways seem viable:

  • You can install a seccomp filter without NO_NEW_PRIVS, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n perform an execve() with a privilege transition (SELinux exec transition, setuid/setgid execution, or execution with permitted file capability set). The seccomp filter can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n force specific syscalls to fail with error number 0 - which e.g. in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case of open() means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process will believe that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syscall succeeded and allocated file descriptor 0. This attack works here, but is a bit messy.
  • You can instruct cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to use a file you control as high-priority swap device, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n create memory pressure. Once cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel writes stack or heap pages from a sufficiently privileged process into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 swap file, you can edit cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 swapped-out memory, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n let cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process load it back. Downsides of this technique are that it is very unpredictable, it involves memory pressure (which could potentially cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system to kill processes you want to keep, and probably destroys many forensic artifacts in RAM), and requires some way to figure out which swapped-out pages belong to which process and are used for what. This requires cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to support swap.
  • You can use pivot_root() to replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 root directory of eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current mount namespace or a newly created mount namespace, bypassing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux checks that would have been performed for mount(). Doing it for a new mount namespace is useful if you only want to affect a child process that elevates its privileges afterwards. This doesn't work if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 root filesystem is a rootfs filesystem. This is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 technique used here.

In recent Android versions, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mechanism used to create dumps of crashing processes has changed: Instead of asking a privileged daemon to create a dump, processes execute one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 helpers /system/bin/crash_dump64 and /system/bin/crash_dump32, which have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux label u:object_r:crash_dump_exec:s0. Currently, when a file with such a label is executed by any SELinux domain, an automatic domain transition to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash_dump domain is triggered (which automatically implies setting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 AT_SECURE flag in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 auxiliary vector, instructing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 linker of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new process to be careful with environment variables like LD_PRELOAD):

domain_auto_trans(domain, crash_dump_exec, crash_dump);

At cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time this bug was reported, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash_dump domain had cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following SELinux policy:

[...]
allow crash_dump {
 domain
 -init
 -crash_dump
 -keystore
 -logd
}:process { ptrace signal sigchld sigstop sigkill };
[...]
r_dir_file(crash_dump, domain)
[...]

This policy permitted crash_dump to attach to processes in almost any domain via ptrace() (providing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to take over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 DAC controls permit it) and allowed it to read properties of any process in procfs. The exclusion list for ptrace access lists a few TCB processes; but notably, vold was not on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list. Therefore, if we can execute crash_dump64 and somehow inject code into it, we can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n take over vold.

Note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to actually ptrace() a process is still gated by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 normal Linux DAC checks, and crash_dump can't use CAP_SYS_PTRACE or CAP_SETUID. If a normal app managed to inject code into crash_dump64, it still wouldn't be able to leverage that to attack system components because of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UID mismatch.

If you've been reading carefully, you might now wonder whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r we could just place our own binary with context u:object_r:crash_dump_exec:s0 on our fake /data filesystem, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n execute that to gain code execution in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash_dump domain. This doesn't work because vold - very sensibly - hardcodes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MS_NOSUID flag when mounting USB storage devices, which not only degrades cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execution of classic setuid/setgid binaries, but also degrades cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 execution of files with file capabilities and executions that would normally involve automatic SELinux domain transitions (unless cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux policy explicitly opts out of this behavior by granting PROCESS2__NOSUID_TRANSITION).

To inject code into crash_dump64, we can create a new mount namespace with unshare() (using our CAP_SYS_ADMIN capability), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n call pivot_root() to point cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 root directory of our process into a directory we fully control, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n execute crash_dump64. Then cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel parses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ELF headers of crash_dump64, reads cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 path to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 linker (/system/bin/linker64), loads cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 linker into memory from that path (relative to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process root, so we can supply our own linker here), and executes it.

At this point, we can execute arbitrary code in crash_dump context and escalate into vold from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re, compromising cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 TCB. At this point, Android's security policy considers us to have kernel-equivalent privileges; however, to see what you'd have to do from here to gain code execution in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel, this blogpost goes a bit furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r.

From vold to init context

It doesn't look like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is an easy way to get from vold into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real init process; however, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a way into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 init SELinux context. Looking through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SELinux policy for allowed transitions into init context, we find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following policy:

domain_auto_trans(kernel, init_exec, init)

This means that if we can get code running in kernel context to execute a file we control labeled init_exec, on a filesystem that wasn't mounted with MS_NOSUID, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n our file will be executed in init context.

The only code that is running in kernel context is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel, so we have to get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to execute cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 file for us. Linux has a mechanism called "usermode helpers" that can do this: Under some circumstances, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel will delegate actions (such as creating coredumps, loading key material into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel, performing DNS lookups, ...) to userspace code. In particular, when a nonexistent key is looked up (e.g. via request_key()), /sbin/request-key (hardcoded, can only be changed to a different static path at kernel build time with CONFIG_STATIC_USERMODEHELPER_PATH) will be invoked.

Being in vold, we can simply mount our own ext4 filesystem over /sbin without MS_NOSUID, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n call request_key(), and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel invokes our request-key in init context.

The exploit stops at this point; however, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following section describes how you could build on it to gain code execution in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel.

From init context to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel

From init context, it is possible to transition into modprobe or vendor_modprobe context by executing an appropriately labeled file after explicitly requesting a domain transition (note that this is domain_trans(), which permits a transition on exec, not domain_auto_trans(), which automatically performs a transition on exec):

domain_trans(init, { rootfs toolbox_exec }, modprobe)
domain_trans(init, vendor_toolbox_exec, vendor_modprobe)

modprobe and vendor_modprobe have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to load kernel modules from appropriately labeled files:

allow modprobe self:capability sys_module;
allow modprobe { system_file }:system module_load;
allow vendor_modprobe self:capability sys_module;
allow vendor_modprobe { vendor_file }:system module_load;

Android nowadays doesn't require signatures for kernel modules:

walleye:/ # zcat /proc/config.gz | grep MODULE
CONFIG_MODULES_USE_ELF_RELA=y
CONFIG_MODULES=y
# CONFIG_MODULE_FORCE_LOAD is not set
CONFIG_MODULE_UNLOAD=y
CONFIG_MODULE_FORCE_UNLOAD=y
CONFIG_MODULE_SRCVERSION_ALL=y
# CONFIG_MODULE_SIG is not set
# CONFIG_MODULE_COMPRESS is not set
CONFIG_MODULES_TREE_LOOKUP=y
CONFIG_ARM64_MODULE_CMODEL_LARGE=y
CONFIG_ARM64_MODULE_PLTS=y
CONFIG_RANDOMIZE_MODULE_REGION_FULL=y
CONFIG_DEBUG_SET_MODULE_RONX=y

Therefore, you could execute an appropriately labeled file to execute code in modprobe context, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n load an appropriately labeled malicious kernel module from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re.

Lessons learned

Notably, this attack crosses two weakly-enforced security boundaries: The boundary from blkid_untrusted to vold (when vold uses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 UUID provided by blkid_untrusted in a pathname without checking that it resembles a valid UUID) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 boundary from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 TCB (by abusing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zygote's CAP_SYS_ADMIN capability). Software vendors have, very rightly, been stressing for quite some time that it is important for security researchers to be aware of what is, and what isn't, a security boundary - but it is also important for vendors to decide where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y want to have security boundaries and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n rigorously enforce those boundaries. Unenforced security boundaries can be of limited use - for example, as a development aid while stronger isolation is in development -, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y can also have negative effects by obfuscating how important a component is for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overall system.

In this case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 weakly-enforced security boundary between vold and blkid_untrusted actually contributed to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability, racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than mitigating it. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 blkid code had run in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vold process, it would not have been necessary to serialize its output, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 injection of a fake UUID would not have worked.

No comments:

Post a Comment