In early October, it was announced that a critical vulnerability had been found in the libutils library. The libutils library is part of Android OS; it implements various primitive elements that can be used by other applications. The least harmful result that exploiting this vulnerability could lead to is the software that uses the stagefright library and handles MP3/MP4 files crashing.
Although exploits for newly discovered vulnerabilities take a while to appear ‘in the wild’, we believe we should be prepared to detect them even if there have been no reports, as yet, of any such exploits being found. Since a working exploit is needed to develop protection functionality, PoC files are commonly used to implement detection.
In this case, developing detection logic that would cover possible exploits for the vulnerability was complicated by the fact that no PoC files were readily available. Because of this, we decided to do the research and generate a PoC file on our own.
We are going to omit some technical details when discussing our work to prevent cybercriminals from using this information.
We began by looking at the changes made to the source code of libutils in order to close the vulnerability. As it turned out, the following change was among the latest:
Checking input parameters in allocFromUTF8 function of String8 class
It can be seen in the code that if len = SIZE_MAX, this will cause an integer overflow when allocating memory.
We assumed that the following had to be done to cause software that handles MP3 files to malfunction: pass a number equal to SIZE_MAX as the second parameter to the allocFromUTF8 function. The function is called from several places in the String8 class. If you analyze the implementation of the String8 object, you will see that the function of interest to us is called in the following places:
- in the String8 class’s constructor (two implementations are possible);
- in the setTo method (two implementations are possible).
It is also worth noting that in one of the two implementations of the constructor and in one of the two implementations of the setTo method, an input parameter is passed that is subsequently passed to allocFromUTF8. This leads us to another conclusion: we are interested in the code that creates the String8 object and explicitly passes the string length in the class’s constructor or calls the setTo method (specifying the string length).
Based on what we know, the vulnerability is exploited when handling MP3 files. This means that it makes sense to look at the way the String8 class is used in the code responsible for handling MP3 files. This code is easy to find in the following branch: \media\libstagefright\MP3Extractor.cpp.
Use of the String8 class in MP3Extractor.cpp code
One of the first times the class is used is when parsing the MP3 file’s COMM tag (the tag stores information on comments to the MP3 file):
Reading comments from an MP3 file using the vulnerable String8 class
It can be seen in the code that another class, ID3, which is responsible for parsing ID3 data, is used to read strings (we are interested in the getString method).
Before looking at this component’s code, have a look at the COMM tag’s structure (information on this can be found in official documentation — http://id3.org/d3v2.3.0).
Example of the COMM tag from a regular MP3 file
Based on the documentation, we have the following:
COMM – Frame ID
00 00 00 04 – size
00 00 – flags
00 – text encoding
00 00 00 – Language
00 – null terminated short description
74 65 73 74 (test) – actual text
Next, let’s look at the ID3 parser code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
void ID3::Iterator::getString(String8 *id, String8 *comment) const { getstring(id, false); // parse short description if (comment != NULL) { getstring(comment, true); } } void ID3::Iterator::getstring(String8 *id, bool otherdata) const { id->setTo(""); const uint8_t *frameData = mFrameData; if (frameData == NULL) { return; } uint8_t encoding = *frameData; if (mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1) { ..... } size_t n = mFrameSize - getHeaderLength() - 1; // error, overflow possible !!! if (otherdata) { // skip past the encoding, language, and the 0 separator frameData += 4; int32_t i = n - 4; while(--i >= 0 && *++frameData != 0) ; int skipped = (frameData - mFrameData); if (skipped >= (int)n) { return; } n -= skipped; } if (encoding == 0x00) { // ISO 8859-1 convertISO8859ToString8(frameData + 1, n, id); } else if (encoding == 0x03) { // UTF-8 id->setTo((const char *)(frameData + 1), n); } else if (encoding == 0x02) |
It can be seen in the code that, under certain conditions, we can call the setTo method of the String8 class, which will in turn call allocFromUTF8 with a pre-calculated value of n.
It only remains to find out whether we can influence the value of n in any way. And, more specifically, whether we can make certain that -1 (0xFFFFFFFF) is written to n as a result of calculations.
The size of the header depends on the version of the ID3 format.
Now we only need to sort out mFrameSize. The amount of code used to calculate this parameter is sufficiently large. It was established by trial and error that the value of the mFrameSize variable when parsing a file also depends on the COMM tag and the version of the file being parsed.
It follows from this that we have the means to influence the values of two variables from the following expression:
size_t n = mFrameSize — getHeaderLength() – 1
By changing data in the COMM tag, we can influence mFrameSize. Using simple math, we can make certain that the following expression is true:
mFrameSize — getHeaderLength() – 1 = -1
As a result of execution, the following value will be written to the n variable: -1 (0xFFFFFFFF).
Now, all we have to do is pass this value to the setTo function. It can be seen in the code that this method will be called if the encoding field in the COMM tag header has certain values.
Calling the setTo method and passing data size to it
If these conditions are met, we get an MP3 file with a malformed COMM tag. Processing it will result in the stock browser and music player crashing:
Stack trace of a crash when processing an MP3 file with a malformed COMM tag
This means we have successfully created a PoC exploit for the vulnerability in question.
Kaspersky Lab products detect this exploit as HEUR:Exploit.AndroidOS.Stagefright.b.
On the trail of Stagefright 2
cyberg
isn’t a PoC usually one that proves a vulnerability is exploitable?
this is just a proof that the bug exists..
Neil
Is an example mp3 file available please? Not to expoit, just to cause the logged crash demonstrated above.
We have a music player app published on Google Play, if we can protect our users simply by stripping id3 tag fields before passing the file on to the system MediaPlayer then we would like to do that.
Is it just the COMM field or other fields too? We’d prefer to keep core fields like artist/title/album but have the opportunity to sanitise them if needed.