==================================================
Date: 02/28/2018
Title: VideoLAN VLC Media Player <= 2.2.5 'EphemeralCockroach' Heap Overflow
CVE: CVE-2017-8311
Exploit and Writeup: Patrick Z. (SivertPL) [kroppoloe@protonmail.ch]
Vulnerability discovery: Yannay Livneh from CheckPoint Security (yannayl) [yannayl@checkpoint.com]
Download VLC: https://www.videolan.org/vlc/releases/2.2.4.html (for example, any lower or equal to 2.2.5 will do)
CWE: CWE-199 (Heap-Based Buffer Overflow)
==================================================
This is the infamous EphemeralCockroach subtitle vulnerability in VLC Media Player which gained notoriety in mid-2017.
There were no public exploits / writeups on this one so I decided it was worth trying.
Media:
https://threatpost.com/subtitle-hack-leaves-200-million-vulnerable-to-remote-code-execution/125868/
http://www.bbc.com/news/technology-40058175
======
Writeup and details on the vulnerability
======
There is a heap buffer overflow in ParseJSS in modules/demux/subtitle.c.
This function is used in a loop for parsing JacoSUB subtitle format files.
This is a combined heap-overflow condition. Due to double-incrementing of psz_text pointer, the null-byte is skipped thus the program fails to stop the loop
which results in IMMEDIATE overflow of the heap buffer into psz_orig heap chunk.
======
VULNERABLE CODE SNIPPET FROM PATCH DIFF IN 2.2.5.1:
Function ParseJSS in modules/demux/subtitle.c.
This is just a snippet but you can find the vulnerable code near the end of the ParseJSS function.
======
/* All variables in the following code are named using the hungarian notation (type_name, ex. i_size stands for INT size, psz_text stands for a null-terminated string) */
for( ; *psz_text != '\0' && *psz_text != '\n' && *psz_text != '\r'; ) // If psz_text is not null byte, newline or carriage return then this loop will keep going (IMPORTANT, but keep reading)
{
switch( *psz_text )
{
case '{':
p_sys->jss.i_comment++;
break;
case '}':
if( p_sys->jss.i_comment )
{
p_sys->jss.i_comment = 0;
if( (*(psz_text + 1 ) ) == ' ' ) psz_text++;
}
break;
case '~':
if( !p_sys->jss.i_comment )
{
*psz_text2 = ' ';
psz_text2++;
}
break;
case ' ':
case '\t':
if( (*(psz_text + 1 ) ) == ' ' || (*(psz_text + 1 ) ) == '\t' )
break;
if( !p_sys->jss.i_comment )
{
*psz_text2 = ' ';
psz_text2++;
}
break;
case '\\':
if( (*(psz_text + 1 ) ) == 'n' )
{
*psz_text2 = '\n';
psz_text++;
psz_text2++;
break;
}
if( ( toupper((unsigned char)*(psz_text + 1 ) ) == 'C' ) ||
( toupper((unsigned char)*(psz_text + 1 ) ) == 'F' ) )
{
psz_text++;
break;
}
if( (*(psz_text + 1 ) ) == 'B' || (*(psz_text + 1 ) ) == 'b' ||
(*(psz_text + 1 ) ) == 'I' || (*(psz_text + 1 ) ) == 'i' ||
(*(psz_text + 1 ) ) == 'U' || (*(psz_text + 1 ) ) == 'u' ||
(*(psz_text + 1 ) ) == 'D' || (*(psz_text + 1 ) ) == 'N' )
{
psz_text++;
break;
}
/* VULNERABLE CODE BELOW */
if( (*(psz_text + 1 ) ) == '~' || (*(psz_text + 1 ) ) == '{' ||
(*(psz_text + 1 ) ) == '\\' )
psz_text++;
else if( *(psz_text + 1 ) == '\r' || *(psz_text + 1 ) == '\n' || // If it ends with \r or \n, that's OK because there's still \0 afterwards.
*(psz_text + 1 ) == '\0' ) // but not in this case...
{
// We assume *(psz_text + 1) == '\0' at the moment
psz_text++;
/*
* After incrementing the ptr, *psz_text == '\0' at the moment.
* We have reached the end of the string
* RIGHT NOW the code lands on the arrow-marked section.
* FROM: -->
*/
}
break;
default:
if( !p_sys->jss.i_comment )
{
*psz_text2 = *psz_text;
psz_text2++;
}
}
/*
* TO: <--
* It DOESN'T matter what has happened before, it's still going to land in this section.
* We assume *psz_text == '\0' atm, so we have reached the end of the string (and thus the end of the buffer !!)
*/
psz_text++;
/*
* BOOM!
* *(psz_text + 1) = 1 byte overflow into psz_orig (the buffer right after psz_text, look at the original code of the patch)
* BUT The most important aspect of this overflow, is that
* Right now, the check in the loop at the beginning will PASS, because we've skipped the null terminator and the loop will KEEP GOING !!!
*/
}
========
PoC
========
Successful exploitation leading to RCE is difficult, nevertheless possible.
The vulnerability poses a challenge because of:
- All mitigations are enabled (ASLR, DEP etc) and need to be bypassed
- The format is SCRIPTLESS
- Multi threading is implemented
For creating a PoC you need to create a JacoSUB subtitle file and load it in VLC. The specification for this ancient format is below:
http://unicorn.us.com/jacosub/jscripts.html
You need to overflow at least 32 bytes into the psz_orig buffer to crash the player.
======
About me
=======
My name is Patrick and I'm a 17 year old Polish-American wannabe hacker/security researcher.
I understand that I need a lot of patience and practice to master this field.
Therefore I'm looking for a security research team or someone EXPERIENCED in the field to help me out.
My dream is to join a good security-research / ethical hacker group with experienced people.
I've participated in NETGEAR Cash Rewards bug bounty at bugcrowd.com and submitted 8 valid vulnerabilities.
Some of my CVEs are:
- CVE-2017-6077
- CVE-2017-6334
- CVE-2017-6366
All of these are published on exploit-db and these are my first vulnerabilities EVER published (not including bug-bounty ones)
Contact me at kroppoloe@protonmail.ch, I really appreciate ANYONE who wishes to include me in their team!
Thank you, publishing my work on this website means A LOT to me.
=======