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

2020-04-10 约 897 字 预计阅读 5 分钟

声明:本文 【漏洞分析学习之cve-2010-2553】 由作者 NoOne 于 2020-04-10 09:54:44 首发 先知社区 曾经 浏览数 110 次

感谢 NoOne 的辛苦付出!

漏洞分析学习之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这里,我认为就不应该照着那份文档分析

参考文章

<<漏洞战争>>

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now