TL;DR: We found bugs in Samsung Galaxy phones that can be triggered remotely via SMS, which when combined provide opportunities for ransomware peddlers. Samsung Mobile Security Team were quick to fix the issues, providing a decent example of how coordinated disclosure should happen.
What is WAP Push?
According to the link, the Wireless Application Protocol (WAP) suite has been in public operation since 1999. The Wireless Datagram Protocol (WDP) which forms part of the WAP suite provides a UDP-like layer to transport data between two endpoints on specific ports. WDP itself can be transported over many protocols, including SMS (the focus of this blog).
WAP Push is transported on WDP and allows content to be pushed to the device with minimal (or no) user intervention. The data is encoded using WAP Binary XML (WBXML).
Reading the last two paragraphs should awaken your inner vulnerability researching senses, given we are talking about a 17-year old technology transporting arbitrary data, which is potentially received and processed without user interaction. Add to that the fact that the specification for the WBXML encoding standard runs to 30 pages and you have a target that begs to be investigated.
On the downside, this stuff has been around for 17 years, surely all the bugs have been found and fixed by now…right?
WAP Push can be used to transport data for a multitude of applications. One application that caught our eye was the Open Mobile Alliance Client Provisioning (OMA CP) protocol that allows remote device provisioning and configuration. This sounds like a powerful capability, so let’s dig deeper!
The devices we had to hand for our research were a series of Samsung Galaxy devices and so the remainder of this blog will be Samsung-centric. It is left as an exercise for the reader to investigate how this technology is handled by other vendors!
Given the potential power of the OMA CP protocol, you would hope and expect that there is some level of authentication built in to stop the device blindly accepting configuration messages from anyone. Indeed, contained within the OMA CP spec is the following:
“The connectivity media type may contain security information, which is transported as parameters to the media type application/vnd.wap.connectivity-wbxml. The security information consists of the message authentication code and the security method. The parameters MAC and SEC have been defined for this purpose and these MUST be supported by the WAP client.” - OMA-WAP-TS-ProvCont-V1_1-20090728-A,Section 4.3
Now let’s see if it works in practice. On Samsung Galaxy devices, including the S7 which was the newest device at the time, OMA CP messages are handled by the ‘omacp’ app. We used our SMS test rig to craft some custom OMA CP SMS messages and send them to the devices.
As it turns out, our rig was able to send these messages to these devices and they were received and duly processed, despite no authentication details being present in the message. May we introduce CVE-2016-7991. It appears as though the ‘omacp’ app completely ignores the security field of the message!
In the previous scenario, the user would be presented with a prompt before the OMA CP message was acted upon. Whilst we can assume that many (most?!) users would blindly click accept just to get rid of the message, it is at least one barrier against unauthorized configuration change. The question is: are there any message types that get processed without the user being notified?
We analysed the omacp app to identify any code flows where configurations are accepted without any user interaction. There were some clues that this may be possible, such as a check for “xcpSetBgInstall” which hints towards a possible background install. A function called xcpInstallWifiSetting also appeared to always be called if there were settings within the configuration message. This seemed very interesting, as we hoped that it would allow us to create a trusted Wi-Fi network profile on the phone with credentials we select, thus allowing us to stand up an access point which the phone would automatically connect to.
The app makes use of a native C library "libomacp", which handles the parsing of configuration messages. This would requirea bit of reversing time to understand the message format, but as we’re lazy weideally want a quick test to see what this did. Once the app has parsed the message and established the Wi-Fi networks to create it fires off an intent to the Wi-Fi-service in the Android framework. Fortunately for us there were no permissions defined on the BroadcastReceiver in Wi-Fi-service, which meant we could ignore the WBMXL format for now and create a test app to send an intent to see what this does….. the phone then proceeded to crash, reboot, crash, repeat…
With our phone now enacting groundhog day we took a closer look at what was actually happening. It appears that when the ‘omacp’ app receives an OMA CP message for setting Wi-Fi access point credentials, it fires off an intent without any user interaction that is received by the WifiServiceImpl (WifiServiceImpl.jar) code that resides within the Android framework. This then goes ahead and updates a config file(/data/misc/wifi/default_ap.conf) with the new settings. Fortunately we were able to get an adb connection to the phone, which had previously been rooted,and moved the dodgy file which stopped the phone from crashing. But what exactly in this default_ap.conf file was causing the device to crash so terribly?
Here is an example of a validdefault_ap.conf, it gets written when the SET_WIFI intent is received and read by the framework every time the Wi-Fi state changes, including on boot.
The code that parses this file is contained within the WifiServiceImpl.jar, specifically within the getVendorApInfoFromFile() function.
This code expects each line of the config file to contain a key (ssid, password etc) and a value that is interpreted as a comma-separated list, which is parsed immediately after:
As you can see from this code, the “networkCount” is derived from the number of comma-separated SSIDs, and then an assumption is made that the same number of elements are included in all other comma separated lists in the file. Given that an attacker can control the contents of this file, this assumption can easily be exploited, causing an ArrayIndexOutOfBounds exception to be thrown.
This in itself would not be the end of the world if it were not for the fact that this code is not wrapped in an exception handler, meaning that when the exception occurs, it crashes the entire Android runtime and the phone reboots. As the runtime comes up again it reads the config file once more and the same happens, causing a boot-loop condition of the android runtime.
This was clearly dangerous, as given the lack of permissions on the WifiService BroadcastReceiver, any app could send the com.android.intent.action.SET_WIFI intent and modify the default access point settings which causes the phone to continually crash.
We confirmed that this worked on an S4, S4 Mini, S5 and Note 4, but it did not initially work on an S6 or S7. We later confirmed it was possible on these newer models (more details later). These issues have since been registered as CVE-2016-7988 (WifiService permissions) and CVE-2016-7989 (unhandled exception in Android runtime).
The question then was could we trigger this over-the-air (OTA) and if so, which devices did it affect?
In order to trigger the bug over the air we now need to go back to the omacp app and work out the message format. The app makes use of a native C library "libomacp", which handles the parsing of configuration messages – it’s finally time to crack open IDA and do some properreversing.
After a bit of IDA Pro magic we identified how to construct a WBXML encoded WAP-Push message to set some Wi-Fi settings. In the process we also found a WBXML parsing bug that is registered as CVE-2016-7990.
Side Track: Bonus Bug CVE-2016-7990 - Integer Overflow inlibomacp.so
Luckily, libomacp.so ships with debug symbols which makes analysis in IDA Pro *much* easier…
The parser has two important primitives to understand. The first and simplest is the wssClientProvWbxmlDecoderBufferReadByte(doc,dest) function, which as the name suggests reads a byte into a destination buffer and increments the read (src)pointer. It returns success or fail (0,1).
The second is the wssClientProvWbxmlDecoderBufferReadUint32(doc) function that unpacks a uint32 from a variable length field. It reads up to a total of 5 bytes using the wssClientProvWbxmlDecoderBufferReadByte() function described above, which may seem strange given a uint32 can be a maximum of 4 bytes. It reads a fifth due to the way the variable length encoding works.
Having read a byte, the function treats the lower 7 bits as part of the uint32, and only if the top (8th) bit is set proceeds to read the next byte. Therefore values of < 128 can be represented in only one byte. Assuming that the number is >=128, the current cumulative value of the integer is logically shifted left 7 bits and OR’d with the 7 bits of this new byte. Again, if the top bit is set, another byte is read and the process is repeated. If the top bit is set on the 5th byte, the function returns an error and stores NULL in the return value.
The bug can be found in the wssClientProcWbxmlDecoderMakeStrTable() function that is called as the final act of reading the wbxml header. This function uses the wssClientProvWbxmlDecoderReadUint32() function to read a length field from the input WBXML. It then adds 2 to this and performs a malloc(length+2). Following this it then enters a loop, calling wssClientProvWbxmlDecoderReadByte() length times or until there is no more data to read in the WBXML input.
If the user-controlled length field is sufficiently large, the addition of 2 extra bytes before the malloc() will cause an integer overflow and will result in a very small number of bytes being allocated. Following this allocation, a copy is performed using length rather than length+2 or until the end of the source buffer is reached. This would result in a heap buffer overflow for length >= 0xfffffffe.
In order to exploit this bug, the WBXML header must end with 0xffffffff encoded in the variable length format as 5 bytes, becoming:
0xff 0xff 0xff 0xff 0x7f
Any bytes appearing in the file after this will get written into a heap allocation of 1 byte causing an arbitrary-length heap overflow. In testing, this has overwritten a variety of things including function pointers, giving the potential for remote code execution on devices up to the S5 model. The S6 and S7 models are only vulnerable to attacks via a locally installed malicious app, as explained below.
Back on track: Exposure
The only minor piece of good news is that initially our SET_WIFI bug did not appear to work on the most recent Samsung Galaxy devices, the S6 or S7. The S4, S4 Mini, S5 and Note 4 are pretty old, surely nobody uses them anymore? Hmmmmm.
Let’s look at why this doesn’t work on the S6 and S7. As mentioned earlier, when a Wi-Fi configuration update message is received, the ‘omacp’ app fires off an intent that is picked up by a BroadcastReceiver set up by the WifiServiceImpl module within the Android runtime.
On all the devices we tested, the ‘omacp’ app sent the SET_WIFI intent to:
It turns out that the reason why the crash wasn’t happening with our app on the S6 and S7 was that intentionally or not, the BroadcastReceiver within WifiServiceImpl.jar is listening for com.samsung.android.intent.action.SET_WIFI but the app is still sending the older intentcom.android.intent.action.SET_WIFI. A quick tweak of the app to send this intent and our S6 and S7 are now also continually crashing. However, from a remote vulnerability perspective there would be no way to trigger this on the S6 and S7 as the omacp app will always send the old intent into the ether.
The complexity of exploiting an Android device in recent years has escalated to the point that more often than not a chain of bugs is required to achieve the desired effect. This case is no different and we have shown here that it took two bugs to produce a viable attack vector, combined with some in-depth knowledge of the bespoke message format.
If you have a rooted device, a fix for thisis to simply use adb as the phone is coming up and delete the default_ap.conffile. If your device is not rooted, the only two solutions are to factory reset the phone (losing all your data) or hope that the attacker is kind enough to send you another OMA CP message containing a valid configuration.
Fixes are available
Given the reversible nature of this attack (a second SMS could be sent that restored the device to its unbroken state) it does not require much imagination to construct a potential ransomware scenario for these bugs. Samsung have now released a security update that addresses these amongst other vulnerabilities and as is our usual advice, it is recommended that users prioritise the installation of these updates.
On discovering the bugs, we contacted Samsung Mobile Security and privately notified them of the issues, including proof-of-concepts where necessary. Over the course of the next three months, we worked with a very pro-active team at Samsung to help them understand the issues and get them fixed.
17th June 2016 – Issues disclosed to vendor
21st June 2016 – Received acknowledgement from vendor
28th June 2016 – Received request for further details on one of the bugs
14th July 2016 – Received notification that all but one bug had been fixed
23rd August 2016 – Received notificationfrom vendor that all issues are fixed and that patch would be released in October
7th October 2016 – Received notification from vendor that patch is delayed until Nov 7th.
7th November 2016 – Patches released
- SVE-2016-6542 (Samsung-specificvuln-id)
- CVE-2016-7988 – No Permissionson SET_WIFI Broadcast receiver
- CVE-2016-7989 – Unhandled ArrayIndexOutOfBounds exception in Android Runtime
- CVE-2016-7990 – Integer overflow in libomacp.so
- CVE-2016-7991 – omacp app ignores security fields in OMA CP message