漏洞分析学习之cve-2010-2553

测试环境:

X 推荐环境 备注
操作系统 win_xp_sp3 简体中文版
虚拟机 vmware 15.5
调试器 Windbg Windbg_x86
反汇编器 IDA pro 版本号:7.0
漏洞软件 Windows Media Player 版本号: 9.00.00.4503

开始准备

堆调试还是首先配置环境,这里符号表我是直接下载好离线的,然后设置路径就行了

具体可以自行查找离线符号表,对应版本号为xp_sp3

这里偶然看到大佬发的cdn,跟设置普通符号表路径一样,自行查看设置,我没测试过

http://sym.ax2401.com:9999/symbols/

这里还有一份ftp下载的

https://ftp.acc.umu.se/mirror/archive/ftp.sunet.se/pub/security/vendor/microsoft/winxp/Service_Packs/

自行尝试,我已经下载了一份解决了

至于poc什么的,可以从漏洞战争随书资料获取

基于POC的逆向分析

地址 命令
73b7cbee call iccvid!CVDecompress (73b721ae)
73b722cc rep movs dword ptr es:[edi],dword ptr [esi]

首先,打开Windows Media Player, 附加后,开启页堆

!gflag +hpa

然后打开poc文件,最终在这里断下

通过栈回溯

kb

获取到上一层,通过ub命令找到上一层调用,

这里是7个参数

同样记录下来,这时候通过

sxe ld:iccvid

下断,在加载这个模块的时候断下,因为是动态加载的,没办法直接对其下断,在加载模块下断后,才能下指定地址

再次g运行,然后跟进去,这里我不会根据漏洞战争这本书进行复现,因为一开头就给繁杂的复现过程不是最好的方法,逆向逆向,我觉得逆着来分析才更可靠一些,

首先这是堆溢出,合理的猜测,poc应该是有大量数据复制的时候才会造成堆溢出, 也就是类似于

rep movs dword ptr es:[edi],dword ptr [esi]

我单步不知道单步了多久发觉了最可疑的地方,

前面的都是那种粗略的测试, 这里你看关键数据,0x2000,以及ecx为0x800,还有rep 每次复制的是dword 0x800*4个字节=3200个字节=0x2000个字节,也就是说每次复制0x2000个数据,

此时看到 edi的堆用户数据大小为0x6000,如果每次复制0x2000个数据,超过三次,势必会造成溢出,也就是说我们要找到,哪里是规定复制次数的,暂时记住这个地址

73b722cc

继续单步,他肯定会往回跳

每次循环都会跳到这里,然后往回跳,同时注意的是,往下走几步过户出现了比较,而这里edx为0x414141,不难想到,这里也是poc破坏的地方

追了几次过后,发觉大概都是在做同样的事,取结构,求长度,复制,

看到这里,add的时候,已经到6000了,接下来再次复制便会溢出了,

这是我认为的思路,接下来根据漏洞战争的思路在走一遍

基于POC的过程复现

这个过程照着漏洞战争的思路复现一遍,前面加调试部分不再重复,直接到进入call,进行调试具体细节看漏洞战争,只点出我认为比较重要的点

比较CVID数据长度是否小于0x20,

接下来一堆赋值便是取结构内容了,

截取自漏洞战争

看这里取法,若要取cvid数据长度,便是要取int的后三个字节,也就是类似这种取法,

73b721f0 8a6601          mov     ah,byte ptr [esi+1]
73b721f3 0fb64e03        movzx   ecx,byte ptr [esi+3]
73b721f7 8a4602          mov     al,byte ptr [esi+2]
73b721fa c1e008          shl     eax,8
73b721fd 0bc1            or      eax,ecx

假设esi指向内容为为 0xffbbccee 则 ah=0xcc , al = 0xbb, eax=0xccbb ecx=0xff , shl 就是变成0xccbb00,在 | ecx就是刚好是0xccbbff

千万不要被图迷惑了,图是这样排布的

而内存中不是,内存中为CVID数据长度, FLAG,也就是最后一位才是FLAG

接下来看到ULongSub函数

猜测是减法,还是查询下,在百度无果后,谷歌一下就找到了

微软文档 就是一个减法, 这里ULongSub就是第一个参数 CVID数据长度减去0xA,也就是这里是判断CVID数据长度是否大于0xA

接下来看到他是比较编码条长度是否大于0x10,从原来的结构图可以推断出来

看下esi数据吧,这样好看一些

也就是说

FLAG为0x00

CVID数据长度为0x000068

编码帧宽度为0x0160

编码帧高度为0x0120

编码条数量为0x0010

这里都要从高位开始读起,开头读取错了..

然后0xA过后便是Strip Header了

截取漏洞战争

然后接下来是

这里说实话我不知道这里是干什么的,看书知道是未解压缩的数据与0x16比较,而我也没有找到证据证明,暂时先过,只知道跟0x5e跟0x16比较

然后

这里毋庸置疑又是取结构数据,而且又是取低三个字节,这里取的是0x10,按照文章所说数据结构,应该是不会这么取值的,这里又是取三个字节,google一番还是只有文中提到的那篇文章

我觉得这里结构应该为

编码条id 占1个字节, 编码条数据大小占3个字节,

保存后,再次比较比较编码条id是否大于0x10,我觉得猜测是正确的

这里判断编码条数量是否大于0xC,

这里就是取顶部加底部的Y坐标,一相减便是高度?

又是未知操作,然后跳转回原来的0x73b723af,然后经过一堆跳,跳来跳去,终于到了快复制数据的地方

这里已经增加底部复制数据了

比较未解压数据是否大于0x16

这里是比较id=0x11而不是0x1100?书里说是0x1100,这里为11是不跳转,也就是可以复制数据

终于追到了复制部分

这里看到UserSize也是0x6000,复制多几次的话就会Bomb

而可以知道,

判断 CVID数据长度>= 0x20 同时编码条id为0x11的大于3条就会造成堆溢出,未解压数据超过

基于源码的分析

首先利用断在指定模块处的命令断下后,

利用

lmv m iccvid

获取dll位置以及符号位置

我这里拿到后,将两个文件放在同一目录下,打开ida进行查看

跳转到指定地址73b721ae,这里已经将pdb导入了,很明显漏洞点在这里

但是,打开这个源码并不会让我减轻多少压力,幸亏前面动手调试了,大概个流程还是知道的

首先

然后

这里贴出完整注释代码

signed int __stdcall CVDecompress(unsigned int a1, _BYTE *a2, unsigned int a3, int a4, int a5, int a6, int a7)
{
  unsigned int v7; // ebx
  _BYTE *v8; // esi
  int v9; // ST18_4
  signed int result; // eax
  _BYTE *v11; // esi
  int v12; // eax
  unsigned __int16 v13; // ax
  _BYTE *v14; // esi
  unsigned __int16 v15; // cx
  unsigned __int16 v16; // ax
  unsigned __int16 v17; // cx
  int v18; // eax
  int v19; // edi
  unsigned __int8 *v20; // ecx
  unsigned __int16 v21; // dx
  unsigned int v22; // edx
  signed int v23; // eax
  unsigned int v24; // [esp+Ch] [ebp-20h]
  int v25; // [esp+10h] [ebp-1Ch]
  _BYTE *v26; // [esp+14h] [ebp-18h]
  int v27; // [esp+14h] [ebp-18h]
  int v28; // [esp+18h] [ebp-14h]
  unsigned int v29; // [esp+1Ch] [ebp-10h]
  _BYTE *v30; // [esp+20h] [ebp-Ch]
  unsigned int v31; // [esp+24h] [ebp-8h]
  int v32; // [esp+28h] [ebp-4h]

  v7 = a1;
  v8 = *(_BYTE **)(a1 + 36);
  if ( v8 )
  {
    v9 = a7;
    *(_DWORD *)(a1 + 36) = 0;
    CVDecompress(v7, v8, 0x2446u, 0, 0, 0, v9);
    LocalFree(v8);
  }
  result = 0;
  if ( a3 >= 0x20 )                             // 传入参数为0x68,这里说是CVID数据大小,下面便是获取CVID数据大小并判断
  {
    v11 = a2;
    BYTE1(result) = a2[1];
    LOBYTE(result) = a2[2];
    v12 = (unsigned __int8)a2[3] | (result << 8);
    if ( (signed int)a3 < v12 || (HIBYTE(a3) = *a2, ULongSub(v12, 0xAu, &v29) < 0) )// 判断CVID数据大小是否大于0xA
    {
LABEL_33:
      result = 0;
    }
    else
    {
      HIBYTE(v13) = v11[8];
      v14 = v11 + 10;
      v28 = 0;
      v26 = v14;
      v30 = v14;
      LOBYTE(v13) = *(v14 - 1);
      v25 = v13;
      if ( (signed int)v13 > 0 )
      {
        v32 = 0;
        do
        {
          if ( v29 < 0x16 )                     // 比较未解压数据是否大于0x16
            break;
          HIBYTE(v15) = v14[1];
          LOBYTE(v15) = v14[2];
          v31 = (unsigned __int8)v14[3] | (v15 << 8);// 取数据结构
          if ( v29 < v31 )
            break;
          if ( *v14 == 0x10 || *v14 == 0x11 )   // 比较编码条id
          {
            if ( ULongSub(v31, 0xCu, &a1) < 0 ) // 判断是否大于0xC
              goto LABEL_33;
            HIBYTE(v16) = v14[8];               // 这部分取结构求宽度
            HIBYTE(v17) = v14[4];
            LOBYTE(v16) = v14[9];
            LOBYTE(v17) = v14[5];
            v18 = v16 - v17;
            LOWORD(v18) = *(_WORD *)(v7 + 46) * v18;
            a2 = (_BYTE *)v18;
            if ( v32 && !HIBYTE(a3) && *v14 == 0x11 )// 只有当编码条id为0x11的时候才会进行复制数据
            {
              qmemcpy(
                (void *)(*(_DWORD *)(v7 + 28) + v32),
                (const void *)(*(_DWORD *)(v7 + 28) + v32 - 0x2000),
                0x2000u);
              v14 = v26;
            }
            v19 = (int)(v30 + 12);
            v20 = v14 + 12;
            *(_DWORD *)(v7 + 56) = v32 + *(_DWORD *)(v7 + 32);
            v27 = (int)(v14 + 12);
            *(_DWORD *)(v7 + 60) = a7;
            while ( a1 >= 4 )
            {
              HIBYTE(v21) = v20[1];
              LOBYTE(v21) = v20[2];
              v22 = v20[3] | (v21 << 8);
              v24 = v22;
              if ( a1 < v22 )
                break;
              switch ( *v20 )
              {
                case 0x20u:
                case 0x21u:
                case 0x24u:
                case 0x25u:
                  (*(void (__stdcall **)(int, _DWORD, _DWORD, _DWORD))v7)(
                    v19,
                    *(_DWORD *)(v7 + 56),
                    *(_DWORD *)(v7 + 52),
                    *(_DWORD *)(v7 + 48));
                  break;
                case 0x22u:
                case 0x23u:
                case 0x26u:
                case 0x27u:
                  (*(void (__stdcall **)(int, int, _DWORD, _DWORD))(v7 + 4))(
                    v19,
                    *(_DWORD *)(v7 + 56) + 4096,
                    *(_DWORD *)(v7 + 52),
                    *(_DWORD *)(v7 + 48));
                  break;
                case 0x30u:
                  (*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE *))(v7 + 8))(
                    v7,
                    v19 + 4,
                    v22 - 4,
                    a4,
                    a5,
                    a6,
                    a2);
                  break;
                case 0x31u:
                  (*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE *))(v7 + 16))(
                    v7,
                    v19 + 4,
                    v22 - 4,
                    a4,
                    a5,
                    a6,
                    a2);
                  break;
                case 0x32u:
                  (*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE *))(v7 + 12))(
                    v7,
                    v19 + 4,
                    v22 - 4,
                    a4,
                    a5,
                    a6,
                    a2);
                  break;
                default:
                  break;
              }
              v20 = (unsigned __int8 *)(v24 + v27);
              v23 = 1;
              v19 += v24;
              v27 += v24;
              if ( v24 > 1 )
                v23 = v24;
              a1 -= v23;
            }
            a6 += a7 * (signed __int16)a2;
            ++v28;
            v32 += 0x2000;
          }
          v30 += v31;
          v29 -= v31;
          v14 += v31;
          v26 = v14;
        }
        while ( v28 < v25 );
      }
      result = 1;
    }
  }
  return result;
}

漏洞修复

ms10-055

什么鬼...,居然不支持了,这里根据分析过程猜测

  1. 限制编码条数量
  2. 好像没了...因为就是因为编码条没做好检查,才造成的堆溢出

引用漏洞战争图

漏洞利用

暂时没看到相关漏洞利用\

总结

windbg此次用的知识与往常无差别,不进行总结了,而分析过程还是总结一下

  1. 很考验耐性,我单步分析追了一晚上,追到复制那里,基于POC的过程复现最耗时间
  2. 这跟做逆向有点类似,不过这里需要注意的是那些个结构跟长度
  3. 尽信书则不如无书? 书里有几处地方我认为有些瑕疵,可能是作者故意留下的,也可能是未注意的,像取id这里,我认为就不应该照着那份文档分析

参考文章

<<漏洞战争>>

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖