相关链接:
exploit-db:https://www.exploit-db.com/exploits/18437/
adobe安全公告:http://www.adobe.com/support/security/bulletins/apsb11-21.html
flashplayer全版本:https://helpx.adobe.com/flash-player/kb/archived-flash-player-versions.html#playerglobal
mp4格式解析:http://blog.csdn.net/chenchong_219/article/details/44263691
http://blog.csdn.net/zhuweigangzwg/article/details/17223355
AVCC:http://blog.csdn.net/romantic_energy/article/details/50508332
H264:http://blog.csdn.net/xiaojun111111/article/details/40107559
指数哥伦布编码:http://blog.csdn.net/u012188065/article/details/53590641
SPS参数解析:http://blog.csdn.net/heanyu/article/details/6205390
漏洞介绍:
根据安全公告的说明,漏洞影响 10.3.181.36 及之前的版本。结合exploit-db的文章,从上面的链接中下载稍微早一点的版本。
环境介绍:
(1) XP Professional sp 3 (2) Flash Player 10.3.181.34
漏洞重现:
下载后发现多个版本的FlashPlayer,了解后 sa是独立的player,不运行在浏览器上。winax代表ActiveX用于IE浏览器。
于是选择安装IE浏览器的FlashPlayer。安装后下载exploit-db上提供的 python 脚本及文章中提到的zip。执行后生成exploit.html 和 exploit.mp4,还需要压缩包中的mediaplayer.swf。
打开exploit.html,用 windbg 附加,允许运行ActiveX控件后,发生内存访问异常在Flash10u.ocx模块如下:
在IDA中定位该异常点,发现是一个循环。先输出日志了解循环流程。执行 bp Flash10u+0x5b4e2 ".printf \"eax=%08x and ecx=%08x\\n\",eax,ecx;g",一开始该模块并未加载所以要先执行sxe ld Flash10u ,等到断下后再下断。
日志中eax一直为0,ecx循环递增4。所以定位一下第一个ecx的值和循环变量。
首次断下时,ebp为0,循环的条件是 ebp < [esi+4Ch] ,[esi+4Ch] 为 0x0FFFFFFF。
ecx是一个栈地址,由于[esi+4Ch]的值过大,导致内存访问异常。怀疑是一个和文件格式有关的漏洞。
在网上了解mp4的格式后,用010Editor打开mp4文件,并用 mp4 的模板解析。
定位到脚本中的特殊数据,这一块数据位于moov box -> trak box -> mdia box(模板未分析其中的内容)-> minf box -> stsd box ->avc1 box ->avcC box
在avcC box的数据中,首先是一个 AVC sequence header 结构如下:
从偏移0x213开始该结构体,偏移 0x219 开始就是脚本中的异常数据,也是文章标题提到的 SequenceParameterSetNALUnit,这和H264编码有关,一个高度压缩数字视频编解码器标准。
SequenceParameterSetNALUnit 它是(sps_size +sps)结构,
# SPSUnit = SPSUnit Len (2 bytes) + NAL Header (1 byte) + profile_idc (1 byte) + Flags and Reserved (1 byte) + levelidc (1 byte) + # seq_parameter_set_id (variable) + log2_max_frame_num_minus4 (variable) + pic_order_cnt_type = 1 (variable) + # delta_pic_order_always_zero_flag (1 bit) + offset_for_non_ref_pic (num_ref_frames_in_pic_order_cnt_cycle) + offset_for_top_to_bottom_field (variable) + # num_ref_frames_in_pic_order_cnt_cycle (num_ref_frames_in_pic_order_cnt_cycle) + other bytes
这里的NAL Header,占一个字节,由三部分组成forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)。0x67 表示序列参数集(SPS)。
继续回去调试,定位到循环值 0x0FFFFFFF 的来源[esi+4Ch],对这个函数调用进行下断调试。
edi看起来有点像一个类假设为 classA,第一个成员指向了 SPSUnit,
由于 H264 解码的过程比较复杂,找到错误的代码位置,也不是很确定错误的数据,尝试对当前函数逆向了解流程。
byte g_byteArr[12] = {0,1,3,7,15,31,63,127,255,0,0,0}; class classA { private byte* pUnit;//0x0 指向SPSUnit private unsigned long unitMaxIndex; //0x4 SPSUnit的最大索引 private unsigned long nowIndex;//0x0C 表示待读取字节的偏移 private unsigned long waitBitCount;//0x10 表示当前字节剩余的待读取的位数 private unsigned short nowByte;//0x14 当前读取的字节 public void sub_1005B396(LPVOID arg_0) { arg_0->member0 = this->getByte(); //获得profile_idc 0x70 //0x34 flag and reserved //001 flag(3bit) this->getBit(); this->getBit(); this->getBit(); //10100 reserved(5bit) arg_0->member4 = this->getBitByCount(5); arg_0->member8 = this->getByte(); //level_idc 0x32 //0x74 011 10100 根据前3位解码后的值为2 this->memberC = this->getUe(); //seq_parameter_set_id 2 arg_0->member10 = 1; //byte arg_0->member14 = 0; arg_0->member18 = 0; arg_0->member1C = 0; //byte arg_0->member1D = 0; //byte BYTE profile_idc = arg_0->member0; if(profile_idc != 0x42 && profile_idc !=0x4D && profile_idc !=0x58) { //1 0100 arg_0->member10 = this->getUe(); //chroma_format_idc 0 if(arg_0->member10 == 3) this->getBit(); //010 0 arg_0->member14 = this->getUe(); //bit_depth_luma_minus8 1 //0x70 0+01110000 00111 0000 arg_0->member18 = this->getUe();//bit_depth_chroma_minus8 6 //0 000 arg_0->member1C = this->getBit(); //lossless_qpprime_flag 0 //0 00 arg_0->member1D = this->getBit(); //seq_scaling_matrix_present_flag 0 if(arg_0->member1D != 0) { loc_1005B441 } } loc_1005B464 if(profile_idc != 0x53 && profile_idc != 0x56) { //00+0x0000AF8888=00+00000000000000001010111110001000100 01000 连续18个0 //1010111110001000100 -1 = 1010111110001000011 = 0x57c43 arg_0->member20 = this->getUe();//log2_max_frame_num_minus4 0x00057c43 //01000 010 00 arg_0->member10 = this->getUe();//pic_order_cnt_type 0x1 if(arg_0->member10 == 0) { }elseif(arg_0->member10 == 1){ loc_1005B49D //0 0 arg_0->member48 = this->getBit(); //delta_pic_order_always_zero_flag 0 //0+0x84 010 000100 arg_0->member54 = this->getSe(); //offset_for_non_ref_pic 010 //000100+0x00 = 00010000000000 = 0001000 0000000 arg_0->member50 = this->getSe(); //offset_for_top_to_bottom_field 000100 //由于0x0003是防竞争机制所以直接跳过0x03 //0000000 +0x000004 = 0000000 + 00000000 + 00000000+00000100 //这个值过大导致后面的循环超出写入范围。 arg_0->member4C = this->getUe(); //num_ref_frames_in_pic_order_cnt_cycle 0x0FFFFFFF int i =0;//ebp unsigned long* p = &arg_0; while(arg_0->member4C > i) { *p = this->getSe(); //offset_for_ref_frame[i] p++; i++; } //省略........ } } } /* 以有符号指数哥伦布熵解码下个值 IDA:sub_1005AA93 */ public int getSe() { //000011111 int result = this->getUe();//eax 11110 int tmp = result; //ecx result++; //恢复Ue最后一步的减1 11111 result>>1; //去掉符号位 1111 if(!(tmp & 0x1))//之前最低位符号位为1 { result = ~result; } return result; } /* 以无符号指数哥伦布熵算法解码下一个值 IDA:sub_1005AA64 */ public unsignd long getUe() { int i = 0; //esi //得到连续的i个0 while(getBit() == 0 && i < 32) { i++; } //读取之后的N+1位的值,已知下一位是1,再获取后面的N位 int r = getBitByCount(i);//再读取 //X-1获取解码后的值 return (r + (1<<(i&0xFF))-1); } /* 得到指定位数的数据 IDA:sub_1005A9C9 count - 需要读取的位数 */ public unsigned long getBitByCount(int count) { unsigned long waitBitCount = this->waitBitCount; //eax unsigned long data = 0; //eax unsigned long tmpCount = count;//ebx unsigned long result = 0;//edi if(tmpCount >= waitBitCount)//需要的位数 >= 当前字节剩余的待读取的位数 { result = g_byteArr[waitBitCount];// 假设waitBitCount = 5 取出0x1f 00011111 result &= this->nowByte; //edi 得到当前字节未读取的位数 tmpCount -= waitBitCount; //tmpCount用于保存还需要读取的位数 //剩余的待读取位数>=8 if(tmpCount >= 8) { count = tmpCount >> 3; //count用于保存还需要读取的字节数 do{ data = this->getByte(); result = (result << 8)|data; tmpCount -= 8; count--; }while(count != 0); } if(tmpCount != 0) { data = this->getByte(); this->nowByte = data; waitBitCount = (8-tmpCount); data >> waitBitCount; this->waitBitCount = waitBitCount; data &= g_byteArr[tmpCount]; result << waitBitCount; result |=data; }else{ this->waitBitCount = 0; } }else{ result = this->nowByte; waitBitCount -= tmpCount; this->waitBitCount = waitBitCount; data = g_byteArr[tmpCount]; result >> waitBitCount; result &= data; } return result; } /* 读取下一个bit IDA:sub_1005A99A */ public int getBit() { if(this->waitBitCount == 0) { this->nowByte = getByte() & 0x000000FF; this->waitBitCount = 8; } this->waitBitCount--; return (this->nowByte >> this->waitBitCount) & 0x00000001; } /* 读取一个字节 IDA:sub_1005A95B */ public BYTE getByte() { DWORD nowIndex = this->nowIndex; if(nowIndex >= this->unitMaxIndex) //判断是否大于最大索引 return 0; BYTE b = *((LPBYTE)(this->pUnit + nowIndex)); //读取一个字节 nowIndex++; this->nowIndex = nowIndex; //索引加1 指向下一个字节 if(b == 0) //如果读取出来的是0 { this->member8++; //判断是否需要跳过下个字节 if(nowIndex >= this->unitMaxIndex || this->member8 !=2 || *((LPBYTE)(this->pUnit + nowIndex)) != 3)//0x0300为NAL语法的防竞争机制 goto end; this->nowIndex++; } this->member8 = 0; end: return b; } }
由于未限制 num_ref_frames_in_pic_order_cnt_cycle 的大小,导致数组访问越界。